Compare commits
37 Commits
hyprland-l
...
f6026b5cac
| Author | SHA1 | Date | |
|---|---|---|---|
| f6026b5cac | |||
| 34906469b9 | |||
| 3cb0301f9a | |||
| 6f489d14ab | |||
| acae19d9c5 | |||
| c30a67facf | |||
| d48edc9bb8 | |||
| af570360d3 | |||
| 34fd60e8f2 | |||
| f826c6ae75 | |||
| 1a2b75adcb | |||
| 4e52e81a50 | |||
| df0b7b6db4 | |||
| a7769545f1 | |||
| bb32668387 | |||
| 8ccf5fb7de | |||
| 52861430da | |||
| d9ebb812c5 | |||
| 5cf2eda008 | |||
| 6299ad2c7d | |||
| 672cc14713 | |||
| 64c45e1060 | |||
| a5413331d9 | |||
| 1044565bf7 | |||
| d684f6fbc5 | |||
| 71deb64ed0 | |||
| bb909849bd | |||
| a37e83fb23 | |||
| 53d8a69a31 | |||
| 87fd1681e2 | |||
| 8933f8e545 | |||
| ed90130233 | |||
| aa1fbf9699 | |||
| 3e05939ce3 | |||
| 8e2128b8d4 | |||
| 1696845579 | |||
| 5522b8bacd |
@@ -125,7 +125,7 @@ branch exposes `hl.plugin.hyprexpo.expo(...)`, so the Lua config can invoke
|
|||||||
|
|
||||||
## 6. Scratchpads
|
## 6. Scratchpads
|
||||||
|
|
||||||
- [x] Preserve named scratchpads: element, gmail, htop, messages, slack,
|
- [x] Preserve named scratchpads: element, htop, slack,
|
||||||
spotify, transmission, volume.
|
spotify, transmission, volume.
|
||||||
- [x] Preserve dropdown terminal scratchpad.
|
- [x] Preserve dropdown terminal scratchpad.
|
||||||
- [x] Scratchpads near-fullscreen and centered.
|
- [x] Scratchpads near-fullscreen and centered.
|
||||||
|
|||||||
@@ -43,13 +43,13 @@ Required behavior:
|
|||||||
- Moving the focused window to the next empty workspace and following it is a
|
- Moving the focused window to the next empty workspace and following it is a
|
||||||
first-class operation.
|
first-class operation.
|
||||||
- Normal workspaces are bounded to `1..9`.
|
- Normal workspaces are bounded to `1..9`.
|
||||||
- Workspace history is tracked per monitor.
|
|
||||||
- Last-workspace toggle uses the current monitor's workspace history.
|
|
||||||
- Workspace cycling works on the current monitor within the bounded workspace
|
|
||||||
set.
|
|
||||||
|
|
||||||
Important behavior:
|
Important behavior:
|
||||||
|
|
||||||
|
- Workspace history is tracked per monitor.
|
||||||
|
- Last-workspace toggle uses the current monitor's workspace history.
|
||||||
|
- Workspace history cycling works on the current monitor within the bounded
|
||||||
|
workspace set.
|
||||||
- Swapping the current workspace contents with another workspace is available.
|
- Swapping the current workspace contents with another workspace is available.
|
||||||
- Moving a window to an empty workspace on another monitor is available.
|
- Moving a window to an empty workspace on another monitor is available.
|
||||||
- Moving the focused window to another monitor without following keeps keyboard
|
- Moving the focused window to another monitor without following keeps keyboard
|
||||||
@@ -62,6 +62,30 @@ Important behavior:
|
|||||||
- Hidden/special workspaces are excluded from the status bar's normal workspace
|
- Hidden/special workspaces are excluded from the status bar's normal workspace
|
||||||
list.
|
list.
|
||||||
|
|
||||||
|
### Workspace History Cycling
|
||||||
|
|
||||||
|
Important behavior:
|
||||||
|
|
||||||
|
- The model is most-recently-used workspace switching, scoped to the monitor
|
||||||
|
where the action starts.
|
||||||
|
- Each monitor has its own ordered workspace history. The focused monitor's
|
||||||
|
history is not shared with other monitors.
|
||||||
|
- Only ordinary bounded workspaces are candidates. Special, scratchpad,
|
||||||
|
minimized, hidden, and out-of-range workspaces are excluded.
|
||||||
|
- Starting a cycle freezes the candidate list for that cycle. Previewing
|
||||||
|
workspaces while the cycle is active must not rewrite the history order.
|
||||||
|
- Starting a cycle previews the previous workspace for the current monitor.
|
||||||
|
- Repeating the forward cycle action continues farther back through that
|
||||||
|
monitor's frozen history.
|
||||||
|
- A reverse cycle action moves through the same frozen history in the opposite
|
||||||
|
direction.
|
||||||
|
- Releasing the initiating modifier key commits the currently previewed
|
||||||
|
workspace and updates history exactly once.
|
||||||
|
- A cancel path may return to the workspace where the cycle started.
|
||||||
|
|
||||||
|
This behavior is important for workflow continuity, but it is not a hard
|
||||||
|
requirement for a minimal daily-driver window manager.
|
||||||
|
|
||||||
## Directional Navigation
|
## Directional Navigation
|
||||||
|
|
||||||
Required behavior:
|
Required behavior:
|
||||||
@@ -109,6 +133,9 @@ Required behavior:
|
|||||||
- Dialogs are centered.
|
- Dialogs are centered.
|
||||||
- There is a command to jump directly to the columns layout and one to jump
|
- There is a command to jump directly to the columns layout and one to jump
|
||||||
directly to the tabbed/fullscreen layout.
|
directly to the tabbed/fullscreen layout.
|
||||||
|
- `Super+Ctrl+Space` jumps directly to the tabbed/fullscreen layout.
|
||||||
|
- Direct fullscreen or floating-fullscreen behavior should not have a
|
||||||
|
keybinding.
|
||||||
- Layout state is per workspace when the compositor supports it.
|
- Layout state is per workspace when the compositor supports it.
|
||||||
|
|
||||||
Important behavior:
|
Important behavior:
|
||||||
@@ -118,7 +145,6 @@ Important behavior:
|
|||||||
Nice behavior:
|
Nice behavior:
|
||||||
|
|
||||||
- Gaps can be toggled.
|
- Gaps can be toggled.
|
||||||
- Fullscreen can be toggled.
|
|
||||||
- Smart borders can be toggled.
|
- Smart borders can be toggled.
|
||||||
- Layout-related modifiers remain available for experiments.
|
- Layout-related modifiers remain available for experiments.
|
||||||
- Inactive windows are slightly dimmed when supported.
|
- Inactive windows are slightly dimmed when supported.
|
||||||
@@ -153,9 +179,7 @@ Important behavior:
|
|||||||
Required behavior:
|
Required behavior:
|
||||||
|
|
||||||
- A named scratchpad exists for element.
|
- A named scratchpad exists for element.
|
||||||
- A named scratchpad exists for gmail.
|
|
||||||
- A named scratchpad exists for htop.
|
- A named scratchpad exists for htop.
|
||||||
- A named scratchpad exists for messages.
|
|
||||||
- A named scratchpad exists for slack.
|
- A named scratchpad exists for slack.
|
||||||
- A named scratchpad exists for spotify.
|
- A named scratchpad exists for spotify.
|
||||||
- A named scratchpad exists for transmission.
|
- A named scratchpad exists for transmission.
|
||||||
@@ -274,12 +298,14 @@ Required behavior:
|
|||||||
- `Super+p` opens the application launcher.
|
- `Super+p` opens the application launcher.
|
||||||
- `Super+Shift+p` opens the run menu.
|
- `Super+Shift+p` opens the run menu.
|
||||||
- `Super+Shift+Return` opens a terminal.
|
- `Super+Shift+Return` opens a terminal.
|
||||||
|
- `Super+q` reloads the window manager config.
|
||||||
|
- `Super+Shift+c` closes the focused window.
|
||||||
|
- `Super+Shift+q` exits the window manager session.
|
||||||
- `Super+Tab` opens the overview.
|
- `Super+Tab` opens the overview.
|
||||||
- `Super+Shift+Tab` opens the overview in bring-window mode when supported.
|
- `Super+Shift+Tab` opens the overview in bring-window mode when supported.
|
||||||
- `Super+g` opens the go-to-window picker.
|
- `Super+g` opens the go-to-window picker.
|
||||||
- `Super+b` opens the bring-window picker.
|
- `Super+b` opens the bring-window picker.
|
||||||
- `Super+Shift+b` opens the replace-window picker.
|
- `Super+Shift+b` opens the replace-window picker.
|
||||||
- `Super+\` toggles to the previous workspace on the current monitor.
|
|
||||||
- `Super+Shift+e` moves the focused window to the next empty workspace and
|
- `Super+Shift+e` moves the focused window to the next empty workspace and
|
||||||
follows it. This is the target replacement for the older `Super+Shift+h`
|
follows it. This is the target replacement for the older `Super+Shift+h`
|
||||||
binding.
|
binding.
|
||||||
@@ -287,6 +313,13 @@ Required behavior:
|
|||||||
- `Hyper+5` swaps the current workspace with a selected workspace.
|
- `Hyper+5` swaps the current workspace with a selected workspace.
|
||||||
- `Hyper+g` gathers windows of the focused class onto the current workspace.
|
- `Hyper+g` gathers windows of the focused class onto the current workspace.
|
||||||
|
|
||||||
|
Important behavior:
|
||||||
|
|
||||||
|
- `Super+\` starts or advances current-monitor workspace history cycling.
|
||||||
|
- `Super+/` reverses current-monitor workspace history cycling while the
|
||||||
|
initiating `Super` key is held.
|
||||||
|
- Releasing the initiating `Super` key commits the workspace history cycle.
|
||||||
|
|
||||||
### Directional Navigation Bindings
|
### Directional Navigation Bindings
|
||||||
|
|
||||||
Required behavior:
|
Required behavior:
|
||||||
@@ -317,9 +350,7 @@ Required behavior:
|
|||||||
Required behavior:
|
Required behavior:
|
||||||
|
|
||||||
- `Super+Alt+e` toggles the element scratchpad.
|
- `Super+Alt+e` toggles the element scratchpad.
|
||||||
- `Super+Alt+g` toggles the gmail scratchpad.
|
|
||||||
- `Super+Alt+h` toggles the htop scratchpad.
|
- `Super+Alt+h` toggles the htop scratchpad.
|
||||||
- `Super+Alt+m` toggles the messages scratchpad.
|
|
||||||
- `Super+Alt+k` toggles the slack scratchpad.
|
- `Super+Alt+k` toggles the slack scratchpad.
|
||||||
- `Super+Alt+s` toggles the spotify scratchpad.
|
- `Super+Alt+s` toggles the spotify scratchpad.
|
||||||
- `Super+Alt+t` toggles the transmission scratchpad.
|
- `Super+Alt+t` toggles the transmission scratchpad.
|
||||||
|
|||||||
2
dotfiles/codex/.gitignore
vendored
2
dotfiles/codex/.gitignore
vendored
@@ -3,3 +3,5 @@
|
|||||||
!AGENTS.md
|
!AGENTS.md
|
||||||
!config.toml
|
!config.toml
|
||||||
!skills
|
!skills
|
||||||
|
|
||||||
|
# Generated/local Codex state, including config.local.toml, stays ignored.
|
||||||
|
|||||||
@@ -2,147 +2,8 @@ model = "gpt-5.5"
|
|||||||
model_reasoning_effort = "high"
|
model_reasoning_effort = "high"
|
||||||
personality = "pragmatic"
|
personality = "pragmatic"
|
||||||
|
|
||||||
|
# Portable Codex defaults. Machine-local additions are appended from
|
||||||
notify = ["/Users/kat/dotfiles/dotfiles/codex/plugins/cache/openai-bundled/computer-use/1.0.755/Codex Computer Use.app/Contents/SharedSupport/SkyComputerUseClient.app/Contents/MacOS/SkyComputerUseClient", "turn-ended"]
|
# dotfiles/codex/config.local.toml by Home Manager.
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/nixpkgs"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/dotfiles"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/railbird"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/subtr-actor"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/google-messages-api"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/scrobble-scrubber"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/temp"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/org-agenda-api"]
|
|
||||||
trust_level = "untrusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/org"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/dotfiles/.git/modules/dotfiles/config/taffybar"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/notifications-tray-icon"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/hyprland"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/git-sync-rs"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/keepbook"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/boxcars"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/rumno"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/git-blame-rank"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/hatchet"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/dotfiles/dotfiles/emacs.d/elpaca/sources/org-project-capture"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/dotfiles/dotfiles/config/taffybar/taffybar/packages"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/scrobble-tools"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/.password-store"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/subtr-actor-mechanics"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/lastfm-edit"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/mova"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/dotfiles/dotfiles/config/taffybar/taffybar"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/rofi-systemd"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/map-quiz"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/run/media/imalison/NETDEBUGUSB"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/coqui-tts-streamer"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Downloads"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/keysmith_generated"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/run/media/imalison/NIXOS_SD"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/Users/kat/dotfiles"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/Users/kat"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/Users/kat/org"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/Users/kat/Documents/Codex/2026-04-25/do-you-see-the-sandisk-external"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/Volumes/Extreme SSD/Projects/keepbook"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/Users/kat/Documents/Codex/2026-04-25/it-seems-like-maybe-we-dont"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/Users/kat/Documents/Codex/2026-04-25/what-is-the-state-of-tiling"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Pictures/ai/2026/celeb"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/.local/share/keepbook"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[notice]
|
|
||||||
hide_gpt5_1_migration_prompt = true
|
|
||||||
"hide_gpt-5.1-codex-max_migration_prompt" = true
|
|
||||||
|
|
||||||
[notice.model_migrations]
|
|
||||||
"gpt-5.2" = "gpt-5.2-codex"
|
|
||||||
|
|
||||||
[mcp_servers.chrome-devtools]
|
[mcp_servers.chrome-devtools]
|
||||||
command = "npx"
|
command = "npx"
|
||||||
@@ -160,16 +21,6 @@ unified_exec = true
|
|||||||
apps = true
|
apps = true
|
||||||
steer = true
|
steer = true
|
||||||
|
|
||||||
[marketplaces.openai-bundled]
|
|
||||||
last_updated = "2026-04-21T17:43:57Z"
|
|
||||||
source_type = "local"
|
|
||||||
source = "/Users/kat/.codex/.tmp/bundled-marketplaces/openai-bundled"
|
|
||||||
|
|
||||||
[marketplaces.openai-primary-runtime]
|
|
||||||
last_updated = "2026-04-25T23:49:36Z"
|
|
||||||
source_type = "local"
|
|
||||||
source = "/Users/kat/.cache/codex-runtimes/codex-primary-runtime/plugins/openai-primary-runtime"
|
|
||||||
|
|
||||||
[plugins."google-calendar@openai-curated"]
|
[plugins."google-calendar@openai-curated"]
|
||||||
enabled = true
|
enabled = true
|
||||||
|
|
||||||
@@ -196,6 +47,3 @@ enabled = true
|
|||||||
|
|
||||||
[plugins."browser-use@openai-bundled"]
|
[plugins."browser-use@openai-bundled"]
|
||||||
enabled = true
|
enabled = true
|
||||||
|
|
||||||
[tui.model_availability_nux]
|
|
||||||
"gpt-5.5" = 4
|
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
general {
|
general {
|
||||||
lock_cmd = pidof hyprlock || hyprlock
|
lock_cmd = pidof hyprlock || hyprlock
|
||||||
before_sleep_cmd = loginctl lock-session
|
before_sleep_cmd = loginctl lock-session
|
||||||
after_sleep_cmd = hyprctl dispatch dpms on
|
|
||||||
}
|
}
|
||||||
|
|
||||||
listener {
|
listener {
|
||||||
timeout = 900
|
timeout = 300
|
||||||
on-timeout = hypr-screensaver stop && hyprctl dispatch dpms off
|
on-timeout = /home/imalison/dotfiles/dotfiles/lib/bin/hypr-screensaver start
|
||||||
on-resume = hyprctl dispatch dpms on
|
on-resume = /home/imalison/dotfiles/dotfiles/lib/bin/hypr-screensaver stop
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,562 +0,0 @@
|
|||||||
# Hyprland Configuration
|
|
||||||
# XMonad-like dynamic tiling using hy3 plugin
|
|
||||||
# Based on XMonad configuration from xmonad.hs
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# PLUGINS (Hyprland pinned to 0.53.0 to match hy3)
|
|
||||||
# =============================================================================
|
|
||||||
# Load the plugin before parsing keybinds/layouts that depend on it
|
|
||||||
plugin = /run/current-system/sw/lib/libhy3.so
|
|
||||||
plugin = /run/current-system/sw/lib/libhyprexpo.so
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# MONITORS
|
|
||||||
# =============================================================================
|
|
||||||
monitor=,preferred,auto,1
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# PROGRAMS
|
|
||||||
# =============================================================================
|
|
||||||
$terminal = ghostty --gtk-single-instance=false
|
|
||||||
$fileManager = dolphin
|
|
||||||
$menu = rofi -show drun -show-icons
|
|
||||||
$runMenu = rofi -show run
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# ENVIRONMENT VARIABLES
|
|
||||||
# =============================================================================
|
|
||||||
env = XCURSOR_SIZE,24
|
|
||||||
env = QT_QPA_PLATFORMTHEME,qt5ct
|
|
||||||
# Used by ~/.config/hypr/scripts/* to keep workspace IDs bounded.
|
|
||||||
env = HYPR_MAX_WORKSPACE,9
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# INPUT CONFIGURATION
|
|
||||||
# =============================================================================
|
|
||||||
input {
|
|
||||||
kb_layout = us
|
|
||||||
kb_variant =
|
|
||||||
kb_model =
|
|
||||||
kb_options =
|
|
||||||
kb_rules =
|
|
||||||
|
|
||||||
follow_mouse = 1
|
|
||||||
|
|
||||||
touchpad {
|
|
||||||
natural_scroll = no
|
|
||||||
}
|
|
||||||
|
|
||||||
sensitivity = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# Cursor warping behavior
|
|
||||||
cursor {
|
|
||||||
persistent_warps = true
|
|
||||||
}
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# GENERAL SETTINGS
|
|
||||||
# =============================================================================
|
|
||||||
general {
|
|
||||||
gaps_in = 5
|
|
||||||
gaps_out = 10
|
|
||||||
border_size = 0
|
|
||||||
col.active_border = rgba(edb443ee) rgba(33ccffee) 45deg
|
|
||||||
col.inactive_border = rgba(595959aa)
|
|
||||||
|
|
||||||
# Use hy3 layout for XMonad-like dynamic tiling
|
|
||||||
layout = hy3
|
|
||||||
|
|
||||||
allow_tearing = false
|
|
||||||
}
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# DECORATION
|
|
||||||
# =============================================================================
|
|
||||||
decoration {
|
|
||||||
rounding = 5
|
|
||||||
|
|
||||||
blur {
|
|
||||||
enabled = true
|
|
||||||
size = 3
|
|
||||||
passes = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Fade inactive windows (like XMonad's fadeInactive)
|
|
||||||
active_opacity = 1.0
|
|
||||||
inactive_opacity = 0.9
|
|
||||||
}
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# ANIMATIONS
|
|
||||||
# =============================================================================
|
|
||||||
animations {
|
|
||||||
enabled = yes
|
|
||||||
|
|
||||||
# Hyprland supports bezier curves, not true spring physics.
|
|
||||||
# Use a mild overshoot plus GNOME-like window animation style.
|
|
||||||
bezier = overshoot, 0.05, 0.9, 0.1, 1.1
|
|
||||||
bezier = smoothOut, 0.36, 1, 0.3, 1
|
|
||||||
bezier = smoothInOut, 0.42, 0, 0.58, 1
|
|
||||||
bezier = linear, 0, 0, 1, 1
|
|
||||||
|
|
||||||
# SPEED is in deciseconds (e.g. 6 == 600ms).
|
|
||||||
animation = windows, 1, 6, overshoot, gnomed
|
|
||||||
animation = windowsIn, 1, 6, overshoot, gnomed
|
|
||||||
animation = windowsOut, 1, 5, smoothInOut, gnomed
|
|
||||||
animation = windowsMove, 1, 6, smoothOut
|
|
||||||
animation = border, 0
|
|
||||||
animation = borderangle, 0
|
|
||||||
animation = fade, 1, 5, smoothOut
|
|
||||||
animation = workspaces, 1, 6, smoothOut, slidefade 15%
|
|
||||||
animation = specialWorkspace, 1, 6, smoothOut, slidevert
|
|
||||||
}
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# MASTER LAYOUT CONFIGURATION
|
|
||||||
# =============================================================================
|
|
||||||
master {
|
|
||||||
new_status = slave
|
|
||||||
mfact = 0.5
|
|
||||||
orientation = left
|
|
||||||
}
|
|
||||||
|
|
||||||
# Dwindle layout (alternative - binary tree like i3)
|
|
||||||
dwindle {
|
|
||||||
pseudotile = yes
|
|
||||||
preserve_split = yes
|
|
||||||
}
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# WORKSPACE RULES (SMART GAPS)
|
|
||||||
# =============================================================================
|
|
||||||
# Replace no_gaps_when_only (removed in newer Hyprland)
|
|
||||||
# Remove gaps when there's only one visible tiled window (ignore special workspaces)
|
|
||||||
workspace = w[tv1]s[false], gapsout:0, gapsin:0
|
|
||||||
workspace = f[1]s[false], gapsout:0, gapsin:0
|
|
||||||
|
|
||||||
# Group/tabbed window configuration (built-in alternative to hy3 tabs)
|
|
||||||
group {
|
|
||||||
col.border_active = rgba(edb443ff)
|
|
||||||
col.border_inactive = rgba(091f2eff)
|
|
||||||
|
|
||||||
groupbar {
|
|
||||||
enabled = true
|
|
||||||
font_size = 12
|
|
||||||
height = 22
|
|
||||||
col.active = rgba(edb443ff)
|
|
||||||
col.inactive = rgba(091f2eff)
|
|
||||||
text_color = rgba(091f2eff)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# HY3/HYPREXPO PLUGIN CONFIG
|
|
||||||
# =============================================================================
|
|
||||||
plugin {
|
|
||||||
hy3 {
|
|
||||||
# Disable autotile to get XMonad-like manual control
|
|
||||||
autotile {
|
|
||||||
enable = false
|
|
||||||
}
|
|
||||||
|
|
||||||
# Tab configuration
|
|
||||||
tabs {
|
|
||||||
height = 22
|
|
||||||
padding = 6
|
|
||||||
render_text = true
|
|
||||||
text_font = "Sans"
|
|
||||||
text_height = 10
|
|
||||||
text_padding = 3
|
|
||||||
col.active = rgba(edb443ff)
|
|
||||||
col.inactive = rgba(091f2eff)
|
|
||||||
col.urgent = rgba(ff0000ff)
|
|
||||||
col.text.active = rgba(091f2eff)
|
|
||||||
col.text.inactive = rgba(ffffffff)
|
|
||||||
col.text.urgent = rgba(ffffffff)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hyprexpo {
|
|
||||||
# Always include workspace 1 in the overview grid
|
|
||||||
workspace_method = first 1
|
|
||||||
# Only show workspaces with windows
|
|
||||||
skip_empty = true
|
|
||||||
# Show numeric workspace labels in the expo grid
|
|
||||||
show_workspace_numbers = true
|
|
||||||
# 3 columns -> 3x3 grid when 9 workspaces are visible
|
|
||||||
columns = 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# MISC
|
|
||||||
# =============================================================================
|
|
||||||
misc {
|
|
||||||
force_default_wallpaper = 0
|
|
||||||
disable_hyprland_logo = true
|
|
||||||
}
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# BINDS OPTIONS
|
|
||||||
# =============================================================================
|
|
||||||
binds {
|
|
||||||
# Keep workspace history so "previous" can toggle back reliably.
|
|
||||||
allow_workspace_cycles = true
|
|
||||||
workspace_back_and_forth = true
|
|
||||||
}
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# WINDOW RULES
|
|
||||||
# =============================================================================
|
|
||||||
# Float dialogs
|
|
||||||
windowrule = match:class ^()$, match:title ^()$, float on
|
|
||||||
windowrule = match:title ^(Picture-in-Picture)$, float on
|
|
||||||
windowrule = match:title ^(Open File)$, float on
|
|
||||||
windowrule = match:title ^(Save File)$, float on
|
|
||||||
windowrule = match:title ^(Confirm)$, float on
|
|
||||||
|
|
||||||
# Rumno OSD/notifications: treat as an overlay, not a "real" managed window.
|
|
||||||
# (Matches both class and title because rumno may set either depending on backend.)
|
|
||||||
windowrule = match:class ^(.*[Rr]umno.*)$, float on
|
|
||||||
windowrule = match:class ^(.*[Rr]umno.*)$, pin on
|
|
||||||
windowrule = match:class ^(.*[Rr]umno.*)$, center on
|
|
||||||
windowrule = match:class ^(.*[Rr]umno.*)$, decorate off
|
|
||||||
windowrule = match:class ^(.*[Rr]umno.*)$, no_shadow on
|
|
||||||
windowrule = match:title ^(.*[Rr]umno.*)$, float on
|
|
||||||
windowrule = match:title ^(.*[Rr]umno.*)$, pin on
|
|
||||||
windowrule = match:title ^(.*[Rr]umno.*)$, center on
|
|
||||||
windowrule = match:title ^(.*[Rr]umno.*)$, decorate off
|
|
||||||
windowrule = match:title ^(.*[Rr]umno.*)$, no_shadow on
|
|
||||||
|
|
||||||
# Scratchpad sizing handled by hyprscratch exec rules (see hyprland.nix)
|
|
||||||
# Using hyprscratch rules instead of windowrule to avoid affecting child windows (e.g. Slack meets)
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# KEY BINDINGS
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
# Modifier keys
|
|
||||||
$mainMod = SUPER
|
|
||||||
$modAlt = SUPER ALT
|
|
||||||
$hyper = SUPER CTRL ALT
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
# Program Launching
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
bind = $mainMod, P, exec, $menu
|
|
||||||
bind = $mainMod SHIFT, P, exec, $runMenu
|
|
||||||
bind = $mainMod SHIFT, Return, exec, $terminal
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
# Overview (Hyprexpo)
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
bind = $mainMod, TAB, hyprexpo:expo, toggle
|
|
||||||
bind = $mainMod SHIFT, TAB, hyprexpo:expo, bring
|
|
||||||
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 (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)
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
bind = $modAlt, E, exec, hyprscratch toggle element
|
|
||||||
bind = $modAlt, G, exec, hyprscratch toggle gmail
|
|
||||||
bind = $modAlt, H, exec, hyprscratch toggle htop
|
|
||||||
bind = $modAlt, M, exec, hyprscratch toggle messages
|
|
||||||
bind = $modAlt, K, exec, hyprscratch toggle slack
|
|
||||||
bind = $modAlt, S, exec, hyprscratch toggle spotify
|
|
||||||
bind = $modAlt, T, exec, hyprscratch toggle transmission
|
|
||||||
bind = $modAlt, V, exec, hyprscratch toggle volume
|
|
||||||
bind = $modAlt, grave, exec, hyprscratch toggle dropdown
|
|
||||||
|
|
||||||
# Hidden workspace (like XMonad's NSP)
|
|
||||||
bind = $mainMod, X, movetoworkspace, special:NSP
|
|
||||||
bind = $mainMod SHIFT, X, togglespecialworkspace, NSP
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
# DIRECTIONAL NAVIGATION (WASD - like XMonad Navigation2D)
|
|
||||||
# Using hy3 dispatchers for proper tree-based navigation
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# Focus movement (Mod + WASD) - hy3:movefocus navigates the tree
|
|
||||||
bind = $mainMod, W, hy3:movefocus, u
|
|
||||||
bind = $mainMod, S, hy3:movefocus, d
|
|
||||||
bind = $mainMod, A, hy3:movefocus, l
|
|
||||||
bind = $mainMod, D, hy3:movefocus, r
|
|
||||||
|
|
||||||
# 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
|
|
||||||
binde = $mainMod CTRL, S, resizeactive, 0 50
|
|
||||||
binde = $mainMod CTRL, A, resizeactive, -50 0
|
|
||||||
binde = $mainMod CTRL, D, resizeactive, 50 0
|
|
||||||
|
|
||||||
# Screen/Monitor focus (Hyper + WASD)
|
|
||||||
bind = $hyper, W, focusmonitor, u
|
|
||||||
bind = $hyper, S, focusmonitor, d
|
|
||||||
bind = $hyper, A, focusmonitor, l
|
|
||||||
bind = $hyper, D, focusmonitor, r
|
|
||||||
|
|
||||||
# Move window to monitor and follow (Hyper + Shift + WASD)
|
|
||||||
bind = $hyper SHIFT, W, movewindow, mon:u
|
|
||||||
bind = $hyper SHIFT, S, movewindow, mon:d
|
|
||||||
bind = $hyper SHIFT, A, movewindow, mon:l
|
|
||||||
bind = $hyper SHIFT, D, movewindow, mon:r
|
|
||||||
|
|
||||||
# Shift to empty workspace on screen direction (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)
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# Create groups with different orientations (like XMonad layouts)
|
|
||||||
# hy3:makegroup creates a split/tab group from focused window
|
|
||||||
bind = $mainMod, Space, hy3:changegroup, toggletab
|
|
||||||
bind = $mainMod SHIFT, Space, hy3:changegroup, opposite
|
|
||||||
|
|
||||||
# Create specific group types
|
|
||||||
bind = $mainMod, H, hy3:makegroup, h
|
|
||||||
bind = $mainMod SHIFT, V, hy3:makegroup, v
|
|
||||||
# Mod+Ctrl+Space mirrors Mod+Space (tabs instead of fullscreen)
|
|
||||||
bind = $mainMod CTRL, Space, hy3:changegroup, toggletab
|
|
||||||
|
|
||||||
# Change group type (cycle h -> v -> tab)
|
|
||||||
bind = $mainMod, slash, hy3:changegroup, h
|
|
||||||
bind = $mainMod SHIFT, slash, hy3:changegroup, v
|
|
||||||
|
|
||||||
# Tab navigation (like XMonad's focus next/prev in tabbed)
|
|
||||||
bind = $mainMod, bracketright, hy3:focustab, r, wrap
|
|
||||||
bind = $mainMod, bracketleft, hy3:focustab, l, wrap
|
|
||||||
|
|
||||||
# Move window within tab group (hy3 has no movetab dispatcher)
|
|
||||||
bind = $mainMod SHIFT, bracketright, hy3:movewindow, r, visible
|
|
||||||
bind = $mainMod SHIFT, bracketleft, hy3:movewindow, l, visible
|
|
||||||
|
|
||||||
# Expand focus to parent group (like XMonad's focus parent)
|
|
||||||
bind = $mainMod, grave, hy3:expand, expand
|
|
||||||
bind = $mainMod SHIFT, grave, hy3:expand, base
|
|
||||||
|
|
||||||
# Fullscreen (like XMonad's NBFULL toggle)
|
|
||||||
bind = $mainMod, F, fullscreen, 0
|
|
||||||
bind = $mainMod SHIFT, F, fullscreen, 1
|
|
||||||
|
|
||||||
# Toggle floating
|
|
||||||
bind = $mainMod, T, togglefloating,
|
|
||||||
|
|
||||||
# Resize split ratio (hy3 uses resizeactive for splits)
|
|
||||||
binde = $mainMod, comma, resizeactive, -50 0
|
|
||||||
binde = $mainMod, period, resizeactive, 50 0
|
|
||||||
|
|
||||||
# Equalize window sizes on workspace (hy3)
|
|
||||||
bind = $mainMod SHIFT, equal, hy3:equalize, workspace
|
|
||||||
|
|
||||||
# Kill group - removes the focused window from its group
|
|
||||||
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
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# Switch workspaces (1-9 only) on the currently focused monitor.
|
|
||||||
bind = $mainMod, 1, focusworkspaceoncurrentmonitor, 1
|
|
||||||
bind = $mainMod, 2, focusworkspaceoncurrentmonitor, 2
|
|
||||||
bind = $mainMod, 3, focusworkspaceoncurrentmonitor, 3
|
|
||||||
bind = $mainMod, 4, focusworkspaceoncurrentmonitor, 4
|
|
||||||
bind = $mainMod, 5, focusworkspaceoncurrentmonitor, 5
|
|
||||||
bind = $mainMod, 6, focusworkspaceoncurrentmonitor, 6
|
|
||||||
bind = $mainMod, 7, focusworkspaceoncurrentmonitor, 7
|
|
||||||
bind = $mainMod, 8, focusworkspaceoncurrentmonitor, 8
|
|
||||||
bind = $mainMod, 9, focusworkspaceoncurrentmonitor, 9
|
|
||||||
|
|
||||||
# Move window to workspace
|
|
||||||
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
|
|
||||||
|
|
||||||
# Move and follow to workspace (like XMonad's shiftThenView)
|
|
||||||
bind = $mainMod CTRL, 1, movetoworkspacesilent, 1
|
|
||||||
bind = $mainMod CTRL, 1, focusworkspaceoncurrentmonitor, 1
|
|
||||||
bind = $mainMod CTRL, 2, movetoworkspacesilent, 2
|
|
||||||
bind = $mainMod CTRL, 2, focusworkspaceoncurrentmonitor, 2
|
|
||||||
bind = $mainMod CTRL, 3, movetoworkspacesilent, 3
|
|
||||||
bind = $mainMod CTRL, 3, focusworkspaceoncurrentmonitor, 3
|
|
||||||
bind = $mainMod CTRL, 4, movetoworkspacesilent, 4
|
|
||||||
bind = $mainMod CTRL, 4, focusworkspaceoncurrentmonitor, 4
|
|
||||||
bind = $mainMod CTRL, 5, movetoworkspacesilent, 5
|
|
||||||
bind = $mainMod CTRL, 5, focusworkspaceoncurrentmonitor, 5
|
|
||||||
bind = $mainMod CTRL, 6, movetoworkspacesilent, 6
|
|
||||||
bind = $mainMod CTRL, 6, focusworkspaceoncurrentmonitor, 6
|
|
||||||
bind = $mainMod CTRL, 7, movetoworkspacesilent, 7
|
|
||||||
bind = $mainMod CTRL, 7, focusworkspaceoncurrentmonitor, 7
|
|
||||||
bind = $mainMod CTRL, 8, movetoworkspacesilent, 8
|
|
||||||
bind = $mainMod CTRL, 8, focusworkspaceoncurrentmonitor, 8
|
|
||||||
bind = $mainMod CTRL, 9, movetoworkspacesilent, 9
|
|
||||||
bind = $mainMod CTRL, 9, focusworkspaceoncurrentmonitor, 9
|
|
||||||
|
|
||||||
# Toggle to the previous workspace on the current monitor using Hyprland's
|
|
||||||
# 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
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# 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
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# Volume control (matching XMonad: Mod+I=up, Mod+K=down, Mod+U=mute)
|
|
||||||
binde = , XF86AudioRaiseVolume, exec, set_volume --unmute --change-volume +5
|
|
||||||
binde = , XF86AudioLowerVolume, exec, set_volume --unmute --change-volume -5
|
|
||||||
bind = , XF86AudioMute, exec, set_volume --toggle-mute
|
|
||||||
binde = $mainMod, I, exec, set_volume --unmute --change-volume +5
|
|
||||||
binde = $mainMod, K, exec, set_volume --unmute --change-volume -5
|
|
||||||
bind = $mainMod, U, exec, set_volume --toggle-mute
|
|
||||||
|
|
||||||
# Media player controls (matching XMonad: Mod+;=play, Mod+L=next, Mod+J=prev)
|
|
||||||
bind = $mainMod, semicolon, exec, playerctl play-pause
|
|
||||||
bind = , XF86AudioPlay, exec, playerctl play-pause
|
|
||||||
bind = , XF86AudioPause, exec, playerctl play-pause
|
|
||||||
bind = $mainMod, L, exec, playerctl next
|
|
||||||
bind = , XF86AudioNext, exec, playerctl next
|
|
||||||
bind = $mainMod, J, exec, playerctl previous
|
|
||||||
bind = , XF86AudioPrev, exec, playerctl previous
|
|
||||||
|
|
||||||
# Mute current window (like XMonad's toggle_mute_current_window)
|
|
||||||
bind = $hyper SHIFT, Q, exec, toggle_mute_current_window.sh
|
|
||||||
bind = $hyper CTRL, Q, exec, toggle_mute_current_window.sh only
|
|
||||||
|
|
||||||
# Brightness control
|
|
||||||
binde = , XF86MonBrightnessUp, exec, brightness.sh up
|
|
||||||
binde = , XF86MonBrightnessDown, exec, brightness.sh down
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
# UTILITY BINDINGS
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
bind = $hyper, V, exec, cliphist list | rofi -dmenu -p "Clipboard" | cliphist decode | wl-copy
|
|
||||||
bind = $hyper, P, exec, rofi-pass
|
|
||||||
bind = $hyper, H, exec, grim -g "$(slurp)" - | swappy -f -
|
|
||||||
bind = $hyper, C, exec, shell_command.sh
|
|
||||||
bind = $hyper, X, exec, rofi_command.sh
|
|
||||||
bind = $hyper SHIFT, L, exec, hyprlock
|
|
||||||
bind = $hyper, K, exec, rofi_kill_process.sh
|
|
||||||
bind = $hyper SHIFT, K, exec, rofi_kill_all.sh
|
|
||||||
bind = $hyper, R, exec, rofi-systemd
|
|
||||||
bind = $hyper, slash, exec, toggle_taffybar
|
|
||||||
bind = $hyper, 9, exec, start_synergy.sh
|
|
||||||
bind = $hyper, I, exec, rofi_select_input.hs
|
|
||||||
bind = $hyper, backslash, exec, /home/imalison/dotfiles/dotfiles/lib/functions/mpg341cx_input toggle
|
|
||||||
bind = $hyper, O, exec, rofi_paswitch
|
|
||||||
bind = $hyper, comma, exec, rofi_wallpaper.sh
|
|
||||||
bind = $hyper, Y, exec, rofi_agentic_skill
|
|
||||||
|
|
||||||
# Reload config
|
|
||||||
bind = $mainMod, R, exec, hyprctl reload
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
# MOUSE BINDINGS
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
bindm = $mainMod, mouse:272, movewindow
|
|
||||||
bindm = $mainMod, mouse:273, resizewindow
|
|
||||||
|
|
||||||
# Scroll through workspaces
|
|
||||||
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
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
# Wire Hyprland into Home Manager's standard user-session targets.
|
|
||||||
# `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 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
|
|
||||||
|
|
||||||
# Clipboard history daemon
|
|
||||||
exec-once = wl-paste --type text --watch cliphist store
|
|
||||||
exec-once = wl-paste --type image --watch cliphist store
|
|
||||||
@@ -3,8 +3,9 @@ local mod_alt = "SUPER + ALT"
|
|||||||
local hyper = "SUPER + CTRL + ALT"
|
local hyper = "SUPER + CTRL + ALT"
|
||||||
|
|
||||||
local terminal = "ghostty --gtk-single-instance=false"
|
local terminal = "ghostty --gtk-single-instance=false"
|
||||||
local menu = "rofi -show drun -show-icons"
|
local shell_ui_command = "hypr_shell_ui"
|
||||||
local run_menu = "rofi -show run"
|
local launcher_command = shell_ui_command .. " launcher"
|
||||||
|
local run_menu = shell_ui_command .. " run"
|
||||||
|
|
||||||
local max_workspace = 9
|
local max_workspace = 9
|
||||||
local scratchpad_top_margin = 60
|
local scratchpad_top_margin = 60
|
||||||
@@ -12,14 +13,19 @@ local columns_layout = "nStack"
|
|||||||
local monocle_layout = "monocle"
|
local monocle_layout = "monocle"
|
||||||
local minimized_workspace = "special:minimized"
|
local minimized_workspace = "special:minimized"
|
||||||
local current_layout = columns_layout
|
local current_layout = columns_layout
|
||||||
|
local enable_nstack = true
|
||||||
|
local enable_hyprexpo = true
|
||||||
|
local enable_hyprwinview = true
|
||||||
|
local enable_workspace_history = true
|
||||||
|
local configure_nstack_plugin_from_lua = false
|
||||||
local workspace_layouts = {}
|
local workspace_layouts = {}
|
||||||
local minimized_windows = {}
|
local minimized_windows = {}
|
||||||
|
local tabbed_workspace_groups = {}
|
||||||
local window_picker_mode = nil
|
local window_picker_mode = nil
|
||||||
local window_picker_candidates = {}
|
local window_picker_candidates = {}
|
||||||
local stack_update_timer = nil
|
local stack_update_timer = nil
|
||||||
local monocle_notice = nil
|
local monocle_notice = nil
|
||||||
local scratchpad_pending = {}
|
local scratchpad_pending = {}
|
||||||
local monitor_workspace_history = {}
|
|
||||||
|
|
||||||
local scratchpads = {
|
local scratchpads = {
|
||||||
htop = {
|
htop = {
|
||||||
@@ -36,7 +42,8 @@ local scratchpads = {
|
|||||||
},
|
},
|
||||||
element = {
|
element = {
|
||||||
command = "element-desktop",
|
command = "element-desktop",
|
||||||
class = "Element",
|
classes = { "Element", "electron" },
|
||||||
|
title = "Element",
|
||||||
},
|
},
|
||||||
slack = {
|
slack = {
|
||||||
command = "slack",
|
command = "slack",
|
||||||
@@ -51,16 +58,6 @@ local scratchpads = {
|
|||||||
class = "com.mitchellh.ghostty.dropdown",
|
class = "com.mitchellh.ghostty.dropdown",
|
||||||
dropdown = true,
|
dropdown = true,
|
||||||
},
|
},
|
||||||
gmail = {
|
|
||||||
command = "google-chrome-stable --new-window https://mail.google.com/mail/u/0/#inbox",
|
|
||||||
class = "google-chrome",
|
|
||||||
title = "Gmail",
|
|
||||||
},
|
|
||||||
messages = {
|
|
||||||
command = "google-chrome-stable --new-window https://messages.google.com/web/conversations",
|
|
||||||
class = "google-chrome",
|
|
||||||
title = "Messages",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
local function command_line_contains(needle)
|
local function command_line_contains(needle)
|
||||||
@@ -84,6 +81,13 @@ local function exec(command)
|
|||||||
return hl.dsp.exec_cmd(command)
|
return hl.dsp.exec_cmd(command)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function window_selector(window)
|
||||||
|
if not window or not window.address then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return "address:" .. tostring(window.address)
|
||||||
|
end
|
||||||
|
|
||||||
local function hyprexpo(action)
|
local function hyprexpo(action)
|
||||||
return function()
|
return function()
|
||||||
if hl.plugin and hl.plugin.hyprexpo and hl.plugin.hyprexpo.expo then
|
if hl.plugin and hl.plugin.hyprexpo and hl.plugin.hyprexpo.expo then
|
||||||
@@ -100,8 +104,40 @@ local function hyprexpo(action)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function hyprwinview(action)
|
||||||
|
return function()
|
||||||
|
if hl.plugin and hl.plugin.hyprwinview and hl.plugin.hyprwinview.overview then
|
||||||
|
hl.plugin.hyprwinview.overview(action)
|
||||||
|
else
|
||||||
|
hl.notification.create({
|
||||||
|
text = "hyprwinview is not loaded",
|
||||||
|
duration = 1800,
|
||||||
|
icon = "warning",
|
||||||
|
color = "rgba(edb443ff)",
|
||||||
|
font_size = 13,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function workspacehistory(action, arg)
|
||||||
|
return function()
|
||||||
|
if hl.plugin and hl.plugin.workspacehistory and hl.plugin.workspacehistory[action] then
|
||||||
|
hl.plugin.workspacehistory[action](arg)
|
||||||
|
else
|
||||||
|
hl.notification.create({
|
||||||
|
text = "workspacehistory is not loaded",
|
||||||
|
duration = 1800,
|
||||||
|
icon = "warning",
|
||||||
|
color = "rgba(edb443ff)",
|
||||||
|
font_size = 13,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local function apply_nstack_config()
|
local function apply_nstack_config()
|
||||||
if verify_config then
|
if verify_config or not enable_nstack or not configure_nstack_plugin_from_lua then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -126,7 +162,7 @@ local function apply_nstack_config()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function apply_hyprexpo_config()
|
local function apply_hyprexpo_config()
|
||||||
if verify_config then
|
if verify_config or not enable_hyprexpo then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -138,6 +174,7 @@ local function apply_hyprexpo_config()
|
|||||||
bg_col = "rgba(111111ff)",
|
bg_col = "rgba(111111ff)",
|
||||||
workspace_method = "center current",
|
workspace_method = "center current",
|
||||||
skip_empty = false,
|
skip_empty = false,
|
||||||
|
max_workspace = max_workspace,
|
||||||
show_workspace_numbers = true,
|
show_workspace_numbers = true,
|
||||||
workspace_number_color = "rgba(edb443ff)",
|
workspace_number_color = "rgba(edb443ff)",
|
||||||
gesture_distance = 200,
|
gesture_distance = 200,
|
||||||
@@ -146,6 +183,60 @@ local function apply_hyprexpo_config()
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function apply_hyprwinview_config()
|
||||||
|
if verify_config or not enable_hyprwinview then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
hl.config({
|
||||||
|
plugin = {
|
||||||
|
hyprwinview = {
|
||||||
|
gap_size = 24,
|
||||||
|
margin = 48,
|
||||||
|
background = "rgba(10101499)",
|
||||||
|
background_blur = 0,
|
||||||
|
border_col = "rgba(ffffff33)",
|
||||||
|
hover_border_col = "rgba(66ccffee)",
|
||||||
|
border_size = 3,
|
||||||
|
window_order = "application",
|
||||||
|
show_app_icon = 1,
|
||||||
|
app_icon_size = 48,
|
||||||
|
app_icon_theme_source = "auto",
|
||||||
|
app_icon_position = "bottom right",
|
||||||
|
app_icon_margin_x = 12,
|
||||||
|
app_icon_margin_y = 12,
|
||||||
|
app_icon_margin_relative_x = 0.0,
|
||||||
|
app_icon_margin_relative_y = 0.0,
|
||||||
|
app_icon_offset_x = 0,
|
||||||
|
app_icon_offset_y = 0,
|
||||||
|
app_icon_backplate_col = "rgba(00000066)",
|
||||||
|
app_icon_backplate_padding = 6,
|
||||||
|
animation = "workspace_zoom",
|
||||||
|
animation_in_ms = 180,
|
||||||
|
animation_out_ms = 140,
|
||||||
|
animation_scale = 0.94,
|
||||||
|
animation_stagger_ms = 16,
|
||||||
|
animation_stagger_max_ms = 120,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if hl.plugin and hl.plugin.hyprwinview and hl.plugin.hyprwinview.configure then
|
||||||
|
hl.plugin.hyprwinview.configure({
|
||||||
|
keys = {
|
||||||
|
left = { "a", "h", "left" },
|
||||||
|
right = { "d", "l", "right" },
|
||||||
|
up = { "w", "k", "up" },
|
||||||
|
down = { "s", "j", "down" },
|
||||||
|
go = { "return", "enter", "space", "g", "f" },
|
||||||
|
bring = { "b", "shift+return", "shift+space" },
|
||||||
|
bring_replace = { "shift + b" },
|
||||||
|
close = { "escape", "q" },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local function active_workspace()
|
local function active_workspace()
|
||||||
return hl.get_active_workspace()
|
return hl.get_active_workspace()
|
||||||
end
|
end
|
||||||
@@ -204,9 +295,22 @@ local function lower_contains(value, needle)
|
|||||||
return value:find(needle, 1, true) ~= nil
|
return value:find(needle, 1, true) ~= nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function lower_contains_any(value, needles)
|
||||||
|
if type(needles) ~= "table" then
|
||||||
|
return lower_contains(value, needles)
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, needle in ipairs(needles) do
|
||||||
|
if lower_contains(value, needle) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
local function scratchpad_window_matches(window, def)
|
local function scratchpad_window_matches(window, def)
|
||||||
return window
|
return window
|
||||||
and lower_contains(window.class, def.class)
|
and lower_contains_any(window.class, def.classes or def.class)
|
||||||
and lower_contains(window.title, def.title)
|
and lower_contains(window.title, def.title)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -219,6 +323,27 @@ local function is_scratchpad_window(window)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function matching_scratchpad_name(window)
|
||||||
|
for name, def in pairs(scratchpads) do
|
||||||
|
if scratchpad_window_matches(window, def) then
|
||||||
|
return name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function same_workspace(left, right)
|
||||||
|
if not left or not right then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if left.name and right.name and tostring(left.name) == tostring(right.name) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
return left.id and right.id and left.id == right.id
|
||||||
|
end
|
||||||
|
|
||||||
local function is_minimized_workspace(workspace)
|
local function is_minimized_workspace(workspace)
|
||||||
if not workspace then
|
if not workspace then
|
||||||
return false
|
return false
|
||||||
@@ -261,6 +386,71 @@ local function tiled_window_count(workspace)
|
|||||||
return #tiled_windows(workspace)
|
return #tiled_windows(workspace)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function sort_windows_by_focus_history(windows)
|
||||||
|
table.sort(windows, function(left, right)
|
||||||
|
return (left.focus_history_id or 0) < (right.focus_history_id or 0)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function window_address_set(windows)
|
||||||
|
local addresses = {}
|
||||||
|
for _, window in ipairs(windows) do
|
||||||
|
if window and window.address then
|
||||||
|
addresses[window.address] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return addresses
|
||||||
|
end
|
||||||
|
|
||||||
|
local function window_address_in_set(window, addresses)
|
||||||
|
return window and window.address and addresses[window.address] or false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function numeric_component(value, key, index)
|
||||||
|
if type(value) ~= "table" then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
return tonumber(value[key] or value[index]) or 0
|
||||||
|
end
|
||||||
|
|
||||||
|
local function window_center(window)
|
||||||
|
local at = window and window.at or {}
|
||||||
|
local size = window and window.size or {}
|
||||||
|
return numeric_component(at, "x", 1) + numeric_component(size, "x", 1) / 2,
|
||||||
|
numeric_component(at, "y", 2) + numeric_component(size, "y", 2) / 2
|
||||||
|
end
|
||||||
|
|
||||||
|
local function window_distance_squared(window, x, y)
|
||||||
|
local wx, wy = window_center(window)
|
||||||
|
local dx = wx - x
|
||||||
|
local dy = wy - y
|
||||||
|
return dx * dx + dy * dy
|
||||||
|
end
|
||||||
|
|
||||||
|
local function grouping_direction(window, anchor)
|
||||||
|
local wx, wy = window_center(window)
|
||||||
|
local ax, ay = window_center(anchor)
|
||||||
|
local dx = wx - ax
|
||||||
|
local dy = wy - ay
|
||||||
|
|
||||||
|
if math.abs(dx) >= math.abs(dy) then
|
||||||
|
return dx >= 0 and "left" or "right"
|
||||||
|
end
|
||||||
|
return dy >= 0 and "up" or "down"
|
||||||
|
end
|
||||||
|
|
||||||
|
local function grouping_directions(window, anchor)
|
||||||
|
local primary = grouping_direction(window, anchor)
|
||||||
|
local directions = { primary }
|
||||||
|
for _, direction in ipairs({ "left", "right", "up", "down" }) do
|
||||||
|
if direction ~= primary then
|
||||||
|
directions[#directions + 1] = direction
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return directions
|
||||||
|
end
|
||||||
|
|
||||||
local function workspace_window_count(workspace_id)
|
local function workspace_window_count(workspace_id)
|
||||||
local workspace = hl.get_workspace(tostring(workspace_id))
|
local workspace = hl.get_workspace(tostring(workspace_id))
|
||||||
if not workspace then
|
if not workspace then
|
||||||
@@ -294,7 +484,7 @@ local function find_empty_workspace(target_monitor, exclude_id)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function update_nstack_count()
|
local function update_nstack_count()
|
||||||
if current_layout ~= columns_layout then
|
if not enable_nstack or current_layout ~= columns_layout then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -303,6 +493,7 @@ local function update_nstack_count()
|
|||||||
if count == 0 then
|
if count == 0 then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
count = math.max(count, 2)
|
||||||
hl.dsp.layout("setstackcount " .. tostring(count))()
|
hl.dsp.layout("setstackcount " .. tostring(count))()
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -386,8 +577,16 @@ local function toggle_columns_monocle()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function active_group_size()
|
||||||
|
local window = hl.get_active_window()
|
||||||
|
return window and window.group and window.group.size or 0
|
||||||
|
end
|
||||||
|
|
||||||
local function monocle_next()
|
local function monocle_next()
|
||||||
if current_layout == monocle_layout then
|
local window = hl.get_active_window()
|
||||||
|
if window and window.group and window.group.size and window.group.size > 1 then
|
||||||
|
hl.dsp.group.next({ window = window_selector(window) })()
|
||||||
|
elseif current_layout == monocle_layout then
|
||||||
hl.dsp.layout("cyclenext")()
|
hl.dsp.layout("cyclenext")()
|
||||||
update_monocle_notice()
|
update_monocle_notice()
|
||||||
else
|
else
|
||||||
@@ -396,7 +595,10 @@ local function monocle_next()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function monocle_prev()
|
local function monocle_prev()
|
||||||
if current_layout == monocle_layout then
|
local window = hl.get_active_window()
|
||||||
|
if window and window.group and window.group.size and window.group.size > 1 then
|
||||||
|
hl.dsp.group.prev({ window = window_selector(window) })()
|
||||||
|
elseif current_layout == monocle_layout then
|
||||||
hl.dsp.layout("cycleprev")()
|
hl.dsp.layout("cycleprev")()
|
||||||
update_monocle_notice()
|
update_monocle_notice()
|
||||||
else
|
else
|
||||||
@@ -405,7 +607,7 @@ local function monocle_prev()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function focus_direction(direction)
|
local function focus_direction(direction)
|
||||||
if current_layout == monocle_layout then
|
if active_group_size() > 1 or current_layout == monocle_layout then
|
||||||
if direction == "up" or direction == "left" then
|
if direction == "up" or direction == "left" then
|
||||||
monocle_prev()
|
monocle_prev()
|
||||||
else
|
else
|
||||||
@@ -417,46 +619,189 @@ local function focus_direction(direction)
|
|||||||
hl.dsp.focus({ direction = direction })()
|
hl.dsp.focus({ direction = direction })()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function swap_direction(direction)
|
||||||
|
if enable_nstack and current_layout == columns_layout and active_group_size() <= 1 then
|
||||||
|
hl.dsp.layout("swapdirection " .. direction)()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
hl.dsp.window.swap({ direction = direction })()
|
||||||
|
end
|
||||||
|
|
||||||
local function focus_workspace(workspace_id)
|
local function focus_workspace(workspace_id)
|
||||||
hl.dsp.focus({ workspace = tostring(workspace_id), on_current_monitor = true })()
|
hl.dsp.focus({ workspace = tostring(workspace_id), on_current_monitor = true })()
|
||||||
end
|
end
|
||||||
|
|
||||||
local function monitor_key(monitor)
|
local function move_window_to_workspace(workspace_id, follow, window)
|
||||||
if not monitor then
|
local target_window = window or hl.get_active_window()
|
||||||
return "unknown"
|
local target_selector = window_selector(target_window)
|
||||||
|
hl.dsp.window.move({ workspace = tostring(workspace_id), follow = false, window = target_selector })()
|
||||||
|
if follow then
|
||||||
|
focus_workspace(workspace_id)
|
||||||
|
if target_selector then
|
||||||
|
hl.dsp.focus({ window = target_selector })()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
return tostring(monitor.name or monitor.id or "unknown")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function remember_workspace_for_monitor(workspace)
|
local function notify_tabbed_group(text)
|
||||||
workspace = workspace or active_workspace()
|
hl.notification.create({
|
||||||
if not workspace or not workspace.id or workspace.id < 1 then
|
text = text,
|
||||||
|
duration = 1800,
|
||||||
|
icon = "info",
|
||||||
|
color = "rgba(edb443ff)",
|
||||||
|
font_size = 13,
|
||||||
|
})
|
||||||
|
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 move_window_into_group(window, anchor)
|
||||||
|
local selector = window_selector(window)
|
||||||
|
if not selector then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, direction in ipairs(grouping_directions(window, anchor)) do
|
||||||
|
hl.dsp.focus({ window = selector })()
|
||||||
|
hl.dsp.window.move({ into_group = direction, window = selector })()
|
||||||
|
|
||||||
|
local active = hl.get_active_window()
|
||||||
|
if active and active.group and active.group.size and active.group.size > 1 then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
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
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local key = monitor_key(workspace.monitor or hl.get_active_monitor())
|
hl.dsp.focus({ window = anchor_selector })()
|
||||||
local history = monitor_workspace_history[key] or {}
|
hl.dsp.group.toggle({ window = anchor_selector })()
|
||||||
if history.current ~= workspace.id then
|
tabbed_workspace_groups[key] = nil
|
||||||
history.previous = history.current
|
set_layout(columns_layout)
|
||||||
history.current = workspace.id
|
schedule_nstack_count_update()
|
||||||
end
|
|
||||||
monitor_workspace_history[key] = history
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function focus_previous_workspace_for_monitor()
|
local function gather_workspace_into_tabbed_group()
|
||||||
local key = monitor_key(hl.get_active_monitor())
|
local workspace = active_workspace()
|
||||||
local history = monitor_workspace_history[key]
|
if not is_normal_workspace(workspace) then
|
||||||
if history and history.previous then
|
return
|
||||||
focus_workspace(history.previous)
|
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)
|
||||||
|
|
||||||
|
hl.dsp.focus({ window = anchor_selector })()
|
||||||
|
hl.dsp.group.toggle({ window = anchor_selector })()
|
||||||
|
|
||||||
|
local group_windows = {}
|
||||||
|
for _, window in ipairs(candidates) do
|
||||||
|
if window ~= anchor and not window.group then
|
||||||
|
group_windows[#group_windows + 1] = window
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local anchor_x, anchor_y = window_center(anchor)
|
||||||
|
table.sort(group_windows, function(left, right)
|
||||||
|
return window_distance_squared(left, anchor_x, anchor_y) < window_distance_squared(right, anchor_x, anchor_y)
|
||||||
|
end)
|
||||||
|
|
||||||
|
local grouped_count = 1
|
||||||
|
for _, window in ipairs(group_windows) do
|
||||||
|
if move_window_into_group(window, anchor) then
|
||||||
|
grouped_count = grouped_count + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if grouped_count <= 1 then
|
||||||
|
hl.dsp.focus({ window = anchor_selector })()
|
||||||
|
hl.dsp.group.toggle({ window = anchor_selector })()
|
||||||
|
notify_tabbed_group("Unable to group tiled windows")
|
||||||
|
return
|
||||||
|
elseif grouped_count < #candidates then
|
||||||
|
notify_tabbed_group("Grouped " .. tostring(grouped_count) .. " of " .. tostring(#candidates) .. " tiled windows")
|
||||||
|
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
|
else
|
||||||
hl.dsp.focus({ workspace = "previous_per_monitor" })()
|
set_layout(columns_layout)
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function move_window_to_workspace(workspace_id, follow, window)
|
|
||||||
hl.dsp.window.move({ workspace = tostring(workspace_id), follow = follow, window = window })()
|
|
||||||
if follow then
|
|
||||||
focus_workspace(workspace_id)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -539,7 +884,7 @@ local function move_window_to_monitor(direction, follow)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local original_monitor = hl.get_active_monitor()
|
local original_monitor = hl.get_active_monitor()
|
||||||
hl.dsp.window.move({ monitor = direction, follow = follow, window = window })()
|
hl.dsp.window.move({ monitor = direction, follow = follow, window = window_selector(window) })()
|
||||||
|
|
||||||
if not follow and original_monitor then
|
if not follow and original_monitor then
|
||||||
hl.dsp.focus({ monitor = original_monitor })()
|
hl.dsp.focus({ monitor = original_monitor })()
|
||||||
@@ -703,15 +1048,16 @@ local function apply_scratchpad_geometry(name, window, target_monitor)
|
|||||||
x = monitor.x + math.floor((monitor.width - width) / 2)
|
x = monitor.x + math.floor((monitor.width - width) / 2)
|
||||||
y = monitor.y + scratchpad_top_margin
|
y = monitor.y + scratchpad_top_margin
|
||||||
end
|
end
|
||||||
|
local selector = window_selector(window)
|
||||||
|
|
||||||
hl.dsp.window.float({ action = "enable", window = window })()
|
hl.dsp.window.float({ action = "enable", window = selector })()
|
||||||
hl.dsp.window.tag({ tag = "+scratchpad", window = window })()
|
hl.dsp.window.tag({ tag = "+scratchpad", window = selector })()
|
||||||
hl.dsp.window.tag({ tag = "+scratchpad-" .. name, window = window })()
|
hl.dsp.window.tag({ tag = "+scratchpad-" .. name, window = selector })()
|
||||||
hl.dsp.window.resize({ x = width, y = height, relative = false, window = window })()
|
hl.dsp.window.resize({ x = width, y = height, relative = false, window = selector })()
|
||||||
hl.dsp.window.move({ x = x, y = y, relative = false, window = window })()
|
hl.dsp.window.move({ x = x, y = y, relative = false, window = selector })()
|
||||||
if def.dropdown then
|
if def.dropdown then
|
||||||
hl.dsp.window.set_prop({ prop = "border_size", value = "0", window = window })()
|
hl.dsp.window.set_prop({ prop = "border_size", value = "0", window = selector })()
|
||||||
hl.dsp.window.set_prop({ prop = "no_shadow", value = "1", window = window })()
|
hl.dsp.window.set_prop({ prop = "no_shadow", value = "1", window = selector })()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -734,13 +1080,35 @@ local function show_scratchpad_window(name, window, workspace, target_monitor)
|
|||||||
|
|
||||||
remove_minimized_window(window)
|
remove_minimized_window(window)
|
||||||
move_window_to_workspace(workspace.id, false, window)
|
move_window_to_workspace(workspace.id, false, window)
|
||||||
hl.dsp.focus({ window = window })()
|
hl.dsp.focus({ window = window_selector(window) })()
|
||||||
schedule_scratchpad_geometry(name, window, target_monitor or hl.get_active_monitor())
|
schedule_scratchpad_geometry(name, window, target_monitor or hl.get_active_monitor())
|
||||||
end
|
end
|
||||||
|
|
||||||
local function scratchpad_is_visible(window)
|
local function scratchpad_is_visible(window)
|
||||||
local workspace = active_workspace()
|
local workspace = active_workspace()
|
||||||
return workspace and window and window.workspace == workspace
|
return workspace and window and same_workspace(window.workspace, workspace)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Active scratchpads are scratchpad windows visible on the active workspace.
|
||||||
|
-- Invoking a different scratchpad replaces that active set.
|
||||||
|
local function active_scratchpad_windows(except_name)
|
||||||
|
local windows = {}
|
||||||
|
for _, window in ipairs(hl.get_windows()) do
|
||||||
|
local name = matching_scratchpad_name(window)
|
||||||
|
if name and name ~= except_name and scratchpad_is_visible(window) then
|
||||||
|
windows[#windows + 1] = {
|
||||||
|
name = name,
|
||||||
|
window = window,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return windows
|
||||||
|
end
|
||||||
|
|
||||||
|
local function hide_active_scratchpads(except_name)
|
||||||
|
for _, active in ipairs(active_scratchpad_windows(except_name)) do
|
||||||
|
hide_scratchpad_window(active.name, active.window)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function adopt_matching_scratchpad_window(window)
|
local function adopt_matching_scratchpad_window(window)
|
||||||
@@ -820,29 +1188,29 @@ local function activate_window_picker_candidate(index)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if mode == "go" then
|
if mode == "go" then
|
||||||
hl.dsp.focus({ window = window })()
|
hl.dsp.focus({ window = window_selector(window) })()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local workspace = active_workspace()
|
local workspace = active_workspace()
|
||||||
if mode == "bring" and workspace then
|
if mode == "bring" and workspace then
|
||||||
move_window_to_workspace(workspace.id, false, window)
|
move_window_to_workspace(workspace.id, false, window)
|
||||||
hl.dsp.focus({ window = window })()
|
hl.dsp.focus({ window = window_selector(window) })()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if mode == "minimized" and workspace then
|
if mode == "minimized" and workspace then
|
||||||
remove_minimized_window(window)
|
remove_minimized_window(window)
|
||||||
restore_minimized_window(window, workspace)
|
restore_minimized_window(window, workspace)
|
||||||
hl.dsp.focus({ window = window })()
|
hl.dsp.focus({ window = window_selector(window) })()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if mode == "replace" then
|
if mode == "replace" then
|
||||||
local focused = hl.get_active_window()
|
local focused = hl.get_active_window()
|
||||||
if focused and focused ~= window then
|
if focused and focused ~= window then
|
||||||
hl.dsp.window.swap({ target = window, window = focused })()
|
hl.dsp.window.swap({ target = window_selector(window), window = window_selector(focused) })()
|
||||||
hl.dsp.focus({ window = window })()
|
hl.dsp.focus({ window = window_selector(window) })()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -939,7 +1307,7 @@ local function focus_next_class()
|
|||||||
local next_class = classes[(current_index % #classes) + 1]
|
local next_class = classes[(current_index % #classes) + 1]
|
||||||
local target = first_by_class[next_class]
|
local target = first_by_class[next_class]
|
||||||
if target then
|
if target then
|
||||||
hl.dsp.focus({ window = target })()
|
hl.dsp.focus({ window = window_selector(target) })()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -978,7 +1346,7 @@ local function raise_or_spawn(class_fragment, command)
|
|||||||
local fragment = string.lower(class_fragment)
|
local fragment = string.lower(class_fragment)
|
||||||
for _, window in ipairs(hl.get_windows()) do
|
for _, window in ipairs(hl.get_windows()) do
|
||||||
if is_normal_window(window) and window.class and string.find(string.lower(window.class), fragment, 1, true) then
|
if is_normal_window(window) and window.class and string.find(string.lower(window.class), fragment, 1, true) then
|
||||||
hl.dsp.focus({ window = window })()
|
hl.dsp.focus({ window = window_selector(window) })()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1008,7 +1376,7 @@ local function restore_last_minimized()
|
|||||||
local window = table.remove(minimized_windows)
|
local window = table.remove(minimized_windows)
|
||||||
if window and window.address and is_minimized_window(window) then
|
if window and window.address and is_minimized_window(window) then
|
||||||
restore_minimized_window(window, workspace)
|
restore_minimized_window(window, workspace)
|
||||||
hl.dsp.focus({ window = window })()
|
hl.dsp.focus({ window = window_selector(window) })()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1074,6 +1442,7 @@ local function toggle_scratchpad(name)
|
|||||||
|
|
||||||
local windows = matching_scratchpad_windows(name)
|
local windows = matching_scratchpad_windows(name)
|
||||||
if #windows == 0 then
|
if #windows == 0 then
|
||||||
|
hide_active_scratchpads(name)
|
||||||
scratchpad_pending[name] = {
|
scratchpad_pending[name] = {
|
||||||
monitor = hl.get_active_monitor(),
|
monitor = hl.get_active_monitor(),
|
||||||
workspace = active_workspace(),
|
workspace = active_workspace(),
|
||||||
@@ -1095,6 +1464,7 @@ local function toggle_scratchpad(name)
|
|||||||
hide_scratchpad_window(name, window)
|
hide_scratchpad_window(name, window)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
hide_active_scratchpads(name)
|
||||||
local workspace = active_workspace()
|
local workspace = active_workspace()
|
||||||
local target_monitor = hl.get_active_monitor()
|
local target_monitor = hl.get_active_monitor()
|
||||||
for _, window in ipairs(windows) do
|
for _, window in ipairs(windows) do
|
||||||
@@ -1103,10 +1473,18 @@ local function toggle_scratchpad(name)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if enable_nstack then
|
||||||
hl.plugin.load("/run/current-system/sw/lib/libhyprNStack.so")
|
hl.plugin.load("/run/current-system/sw/lib/libhyprNStack.so")
|
||||||
if not verify_config then
|
end
|
||||||
|
if enable_hyprexpo and not verify_config then
|
||||||
hl.plugin.load("/run/current-system/sw/lib/libhyprexpo.so")
|
hl.plugin.load("/run/current-system/sw/lib/libhyprexpo.so")
|
||||||
end
|
end
|
||||||
|
if enable_hyprwinview and not verify_config then
|
||||||
|
hl.plugin.load("/run/current-system/sw/lib/libhyprwinview.so")
|
||||||
|
end
|
||||||
|
if enable_workspace_history and not verify_config then
|
||||||
|
hl.plugin.load("/run/current-system/sw/lib/libhypr-workspace-history.so")
|
||||||
|
end
|
||||||
|
|
||||||
hl.env("XCURSOR_SIZE", "24")
|
hl.env("XCURSOR_SIZE", "24")
|
||||||
hl.env("HYPRCURSOR_SIZE", "24")
|
hl.env("HYPRCURSOR_SIZE", "24")
|
||||||
@@ -1158,19 +1536,28 @@ hl.config({
|
|||||||
workspace_back_and_forth = true,
|
workspace_back_and_forth = true,
|
||||||
},
|
},
|
||||||
group = {
|
group = {
|
||||||
|
group_on_movetoworkspace = false,
|
||||||
col = {
|
col = {
|
||||||
border_active = "rgba(edb443ff)",
|
border_active = "rgba(edb443ff)",
|
||||||
border_inactive = "rgba(091f2eff)",
|
border_inactive = "rgba(091f2eff)",
|
||||||
},
|
},
|
||||||
groupbar = {
|
groupbar = {
|
||||||
enabled = true,
|
enabled = true,
|
||||||
font_size = 12,
|
blur = true,
|
||||||
height = 22,
|
font_size = 13,
|
||||||
|
gradients = true,
|
||||||
|
height = 26,
|
||||||
|
indicator_gap = 0,
|
||||||
|
indicator_height = 1,
|
||||||
|
rounding = 5,
|
||||||
|
gradient_rounding = 5,
|
||||||
|
text_padding = 8,
|
||||||
col = {
|
col = {
|
||||||
active = "rgba(edb443ff)",
|
active = "rgba(edb443ff)",
|
||||||
inactive = "rgba(091f2eff)",
|
inactive = "rgba(101820f2)",
|
||||||
},
|
},
|
||||||
text_color = "rgba(091f2eff)",
|
text_color = "rgba(091018ff)",
|
||||||
|
text_color_inactive = "rgba(f2f5f7ff)",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
misc = {
|
misc = {
|
||||||
@@ -1185,15 +1572,50 @@ hl.curve("smoothOut", { type = "bezier", points = { { 0.36, 1 }, { 0.3, 1 } } })
|
|||||||
hl.curve("smoothInOut", { type = "bezier", points = { { 0.42, 0 }, { 0.58, 1 } } })
|
hl.curve("smoothInOut", { type = "bezier", points = { { 0.42, 0 }, { 0.58, 1 } } })
|
||||||
hl.curve("linear", { type = "bezier", points = { { 0, 0 }, { 1, 1 } } })
|
hl.curve("linear", { type = "bezier", points = { { 0, 0 }, { 1, 1 } } })
|
||||||
|
|
||||||
hl.animation({ leaf = "windows", enabled = true, speed = 6, bezier = "overshoot", style = "gnomed" })
|
local animations = {
|
||||||
hl.animation({ leaf = "windowsIn", enabled = true, speed = 6, bezier = "overshoot", style = "gnomed" })
|
{ leaf = "global", enabled = true, speed = 8, bezier = "default" },
|
||||||
hl.animation({ leaf = "windowsOut", enabled = true, speed = 5, bezier = "smoothInOut", style = "gnomed" })
|
|
||||||
hl.animation({ leaf = "windowsMove", enabled = true, speed = 6, bezier = "smoothOut" })
|
{ leaf = "windows", enabled = true, speed = 6, bezier = "overshoot", style = "gnomed" },
|
||||||
hl.animation({ leaf = "border", enabled = false })
|
{ leaf = "windowsIn", enabled = true, speed = 6, bezier = "overshoot", style = "gnomed" },
|
||||||
hl.animation({ leaf = "borderangle", enabled = false })
|
{ leaf = "windowsOut", enabled = true, speed = 5, bezier = "smoothInOut", style = "gnomed" },
|
||||||
hl.animation({ leaf = "fade", enabled = true, speed = 5, bezier = "smoothOut" })
|
{ leaf = "windowsMove", enabled = true, speed = 6, bezier = "smoothOut" },
|
||||||
hl.animation({ leaf = "workspaces", enabled = true, speed = 6, bezier = "smoothOut", style = "slidefade 15%" })
|
|
||||||
hl.animation({ leaf = "specialWorkspace", enabled = true, speed = 6, bezier = "smoothOut", style = "slidevert" })
|
{ leaf = "border", enabled = false },
|
||||||
|
{ leaf = "borderangle", enabled = false },
|
||||||
|
|
||||||
|
{ leaf = "fade", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||||
|
{ leaf = "fadeIn", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||||
|
{ leaf = "fadeOut", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||||
|
{ leaf = "fadeSwitch", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||||
|
{ leaf = "fadeShadow", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||||
|
{ leaf = "fadeGlow", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||||
|
{ leaf = "fadeDim", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||||
|
{ leaf = "fadeLayers", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||||
|
{ leaf = "fadeLayersIn", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||||
|
{ leaf = "fadeLayersOut", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||||
|
{ leaf = "fadePopups", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||||
|
{ leaf = "fadePopupsIn", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||||
|
{ leaf = "fadePopupsOut", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||||
|
{ leaf = "fadeDpms", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||||
|
|
||||||
|
{ leaf = "layers", enabled = true, speed = 5, bezier = "smoothOut", style = "fade" },
|
||||||
|
{ leaf = "layersIn", enabled = true, speed = 5, bezier = "smoothOut", style = "fade" },
|
||||||
|
{ leaf = "layersOut", enabled = true, speed = 5, bezier = "smoothOut", style = "fade" },
|
||||||
|
|
||||||
|
{ leaf = "workspaces", enabled = true, speed = 6, bezier = "smoothOut", style = "slidefade 15%" },
|
||||||
|
{ leaf = "workspacesIn", enabled = true, speed = 6, bezier = "smoothOut", style = "slidefade 15%" },
|
||||||
|
{ leaf = "workspacesOut", enabled = true, speed = 6, bezier = "smoothOut", style = "slidefade 15%" },
|
||||||
|
{ leaf = "specialWorkspace", enabled = true, speed = 6, bezier = "smoothOut", style = "slidevert" },
|
||||||
|
{ leaf = "specialWorkspaceIn", enabled = true, speed = 6, bezier = "smoothOut", style = "slidevert" },
|
||||||
|
{ leaf = "specialWorkspaceOut", enabled = true, speed = 6, bezier = "smoothOut", style = "slidevert" },
|
||||||
|
|
||||||
|
{ leaf = "zoomFactor", enabled = true, speed = 7, bezier = "smoothOut" },
|
||||||
|
{ leaf = "monitorAdded", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, animation in ipairs(animations) do
|
||||||
|
hl.animation(animation)
|
||||||
|
end
|
||||||
|
|
||||||
local function apply_rules()
|
local function apply_rules()
|
||||||
if verify_config then
|
if verify_config then
|
||||||
@@ -1208,6 +1630,10 @@ local function apply_rules()
|
|||||||
hl.window_rule({ match = { title = "^(Open File)$" }, float = true })
|
hl.window_rule({ match = { title = "^(Open File)$" }, float = true })
|
||||||
hl.window_rule({ match = { title = "^(Save File)$" }, float = true })
|
hl.window_rule({ match = { title = "^(Save File)$" }, float = true })
|
||||||
hl.window_rule({ match = { title = "^(Confirm)$" }, float = true })
|
hl.window_rule({ match = { title = "^(Confirm)$" }, float = true })
|
||||||
|
hl.window_rule({
|
||||||
|
match = { class = "^(com\\.mitchellh\\.ghostty\\.dropdown)$" },
|
||||||
|
animation = "slide top",
|
||||||
|
})
|
||||||
hl.window_rule({
|
hl.window_rule({
|
||||||
match = { class = "^(.*[Rr]umno.*)$" },
|
match = { class = "^(.*[Rr]umno.*)$" },
|
||||||
float = true,
|
float = true,
|
||||||
@@ -1226,27 +1652,23 @@ local function apply_rules()
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
apply_rules()
|
bind(main_mod .. " + P", exec(launcher_command))
|
||||||
|
|
||||||
bind(main_mod .. " + P", exec(menu))
|
|
||||||
bind(main_mod .. " + SHIFT + P", exec(run_menu))
|
bind(main_mod .. " + SHIFT + P", exec(run_menu))
|
||||||
|
bind(hyper .. " + SHIFT + N", exec(shell_ui_command .. " control-center"))
|
||||||
|
bind(hyper .. " + CTRL + N", exec(shell_ui_command .. " settings"))
|
||||||
bind(main_mod .. " + SHIFT + Return", exec(terminal))
|
bind(main_mod .. " + SHIFT + Return", exec(terminal))
|
||||||
bind(main_mod .. " + Q", hl.dsp.window.close())
|
bind(main_mod .. " + Q", exec("hyprctl reload"))
|
||||||
bind(main_mod .. " + SHIFT + C", hl.dsp.window.close())
|
bind(main_mod .. " + SHIFT + C", hl.dsp.window.close())
|
||||||
bind(main_mod .. " + SHIFT + Q", hl.dsp.exit())
|
bind(main_mod .. " + SHIFT + Q", hl.dsp.exit())
|
||||||
bind(main_mod .. " + E", exec("emacsclient --eval '(emacs-everywhere)'"))
|
bind(main_mod .. " + E", exec("emacsclient --eval '(emacs-everywhere)'"))
|
||||||
bind(main_mod .. " + V", exec("wl-paste | xdotool type --file -"))
|
bind(main_mod .. " + V", exec("wl-paste | xdotool type --file -"))
|
||||||
bind(main_mod .. " + Tab", hyprexpo("toggle"))
|
bind(main_mod .. " + Tab", hyprwinview("toggle"))
|
||||||
bind(main_mod .. " + SHIFT + Tab", hyprexpo("bring"))
|
bind(main_mod .. " + SHIFT + Tab", hyprwinview("toggle other-workspaces"))
|
||||||
bind(main_mod .. " + G", function()
|
bind("ALT + Tab", hyprexpo("toggle"))
|
||||||
enter_window_picker("go")
|
bind("ALT + SHIFT + Tab", hyprexpo("bring"))
|
||||||
end)
|
bind(main_mod .. " + G", exec(shell_ui_command .. " window go"))
|
||||||
bind(main_mod .. " + B", function()
|
bind(main_mod .. " + B", exec(shell_ui_command .. " window bring"))
|
||||||
enter_window_picker("bring")
|
bind(main_mod .. " + SHIFT + B", exec(shell_ui_command .. " window replace"))
|
||||||
end)
|
|
||||||
bind(main_mod .. " + SHIFT + B", function()
|
|
||||||
enter_window_picker("replace")
|
|
||||||
end)
|
|
||||||
|
|
||||||
bind(main_mod .. " + W", function()
|
bind(main_mod .. " + W", function()
|
||||||
focus_direction("up")
|
focus_direction("up")
|
||||||
@@ -1261,10 +1683,18 @@ bind(main_mod .. " + D", function()
|
|||||||
focus_direction("right")
|
focus_direction("right")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
bind(main_mod .. " + SHIFT + W", hl.dsp.window.swap({ direction = "up" }))
|
bind(main_mod .. " + SHIFT + W", function()
|
||||||
bind(main_mod .. " + SHIFT + S", hl.dsp.window.swap({ direction = "down" }))
|
swap_direction("up")
|
||||||
bind(main_mod .. " + SHIFT + A", hl.dsp.window.swap({ direction = "left" }))
|
end)
|
||||||
bind(main_mod .. " + SHIFT + D", hl.dsp.window.swap({ direction = "right" }))
|
bind(main_mod .. " + SHIFT + S", function()
|
||||||
|
swap_direction("down")
|
||||||
|
end)
|
||||||
|
bind(main_mod .. " + SHIFT + A", function()
|
||||||
|
swap_direction("left")
|
||||||
|
end)
|
||||||
|
bind(main_mod .. " + SHIFT + D", function()
|
||||||
|
swap_direction("right")
|
||||||
|
end)
|
||||||
|
|
||||||
bind(main_mod .. " + CTRL + W", function()
|
bind(main_mod .. " + CTRL + W", function()
|
||||||
move_window_to_monitor("u", false)
|
move_window_to_monitor("u", false)
|
||||||
@@ -1338,17 +1768,11 @@ bind(hyper .. " + SHIFT + D", function()
|
|||||||
move_window_to_monitor("r", true)
|
move_window_to_monitor("r", true)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
bind(main_mod .. " + Space", toggle_columns_monocle)
|
bind(main_mod .. " + Space", gather_workspace_into_tabbed_group)
|
||||||
bind(main_mod .. " + SHIFT + Space", function()
|
bind(main_mod .. " + SHIFT + Space", force_columns_layout)
|
||||||
set_layout(columns_layout)
|
bind(main_mod .. " + CTRL + Space", gather_workspace_into_tabbed_group)
|
||||||
end)
|
|
||||||
bind(main_mod .. " + CTRL + Space", function()
|
|
||||||
set_layout(monocle_layout)
|
|
||||||
end)
|
|
||||||
bind(main_mod .. " + bracketright", monocle_next)
|
bind(main_mod .. " + bracketright", monocle_next)
|
||||||
bind(main_mod .. " + bracketleft", monocle_prev)
|
bind(main_mod .. " + bracketleft", monocle_prev)
|
||||||
bind(main_mod .. " + F", hl.dsp.window.fullscreen({ mode = "fullscreen" }))
|
|
||||||
bind(main_mod .. " + SHIFT + F", hl.dsp.window.fullscreen({ mode = "maximized" }))
|
|
||||||
bind(main_mod .. " + T", hl.dsp.window.float())
|
bind(main_mod .. " + T", hl.dsp.window.float())
|
||||||
bind(main_mod .. " + M", minimize_active_window)
|
bind(main_mod .. " + M", minimize_active_window)
|
||||||
bind(main_mod .. " + SHIFT + M", restore_last_minimized)
|
bind(main_mod .. " + SHIFT + M", restore_last_minimized)
|
||||||
@@ -1371,15 +1795,9 @@ bind(main_mod .. " + SHIFT + X", hl.dsp.workspace.toggle_special("NSP"))
|
|||||||
bind(mod_alt .. " + E", function()
|
bind(mod_alt .. " + E", function()
|
||||||
toggle_scratchpad("element")
|
toggle_scratchpad("element")
|
||||||
end)
|
end)
|
||||||
bind(mod_alt .. " + G", function()
|
|
||||||
toggle_scratchpad("gmail")
|
|
||||||
end)
|
|
||||||
bind(mod_alt .. " + H", function()
|
bind(mod_alt .. " + H", function()
|
||||||
toggle_scratchpad("htop")
|
toggle_scratchpad("htop")
|
||||||
end)
|
end)
|
||||||
bind(mod_alt .. " + M", function()
|
|
||||||
toggle_scratchpad("messages")
|
|
||||||
end)
|
|
||||||
bind(mod_alt .. " + K", function()
|
bind(mod_alt .. " + K", function()
|
||||||
toggle_scratchpad("slack")
|
toggle_scratchpad("slack")
|
||||||
end)
|
end)
|
||||||
@@ -1412,7 +1830,9 @@ for i = 1, 9 do
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
bind(main_mod .. " + backslash", focus_previous_workspace_for_monitor)
|
bind(main_mod .. " + backslash", workspacehistory("cycle", 1))
|
||||||
|
bind(main_mod .. " + slash", workspacehistory("cycle", -1))
|
||||||
|
bind(main_mod .. " + Escape", workspacehistory("cancel"))
|
||||||
bind(main_mod .. " + Z", hl.dsp.focus({ monitor = "+1" }))
|
bind(main_mod .. " + Z", hl.dsp.focus({ monitor = "+1" }))
|
||||||
bind(main_mod .. " + SHIFT + Z", hl.dsp.window.move({ monitor = "+1" }))
|
bind(main_mod .. " + SHIFT + Z", hl.dsp.window.move({ monitor = "+1" }))
|
||||||
bind(main_mod .. " + mouse_down", function()
|
bind(main_mod .. " + mouse_down", function()
|
||||||
@@ -1454,6 +1874,7 @@ bind(hyper .. " + R", exec("rofi-systemd"))
|
|||||||
bind(hyper .. " + slash", exec("toggle_taffybar"))
|
bind(hyper .. " + slash", exec("toggle_taffybar"))
|
||||||
bind(hyper .. " + I", exec("rofi_select_input.hs"))
|
bind(hyper .. " + I", exec("rofi_select_input.hs"))
|
||||||
bind(hyper .. " + backslash", exec("/home/imalison/dotfiles/dotfiles/lib/functions/mpg341cx_input toggle"))
|
bind(hyper .. " + backslash", exec("/home/imalison/dotfiles/dotfiles/lib/functions/mpg341cx_input toggle"))
|
||||||
|
bind(hyper .. " + SHIFT + backslash", workspacehistory("debug"))
|
||||||
bind(hyper .. " + O", exec("rofi_paswitch"))
|
bind(hyper .. " + O", exec("rofi_paswitch"))
|
||||||
bind(hyper .. " + comma", exec("rofi_wallpaper.sh"))
|
bind(hyper .. " + comma", exec("rofi_wallpaper.sh"))
|
||||||
bind(hyper .. " + Y", exec("rofi_agentic_skill"))
|
bind(hyper .. " + Y", exec("rofi_agentic_skill"))
|
||||||
@@ -1465,17 +1886,20 @@ bind(main_mod .. " + mouse:273", hl.dsp.window.resize())
|
|||||||
hl.on("hyprland.start", function()
|
hl.on("hyprland.start", function()
|
||||||
apply_nstack_config()
|
apply_nstack_config()
|
||||||
apply_hyprexpo_config()
|
apply_hyprexpo_config()
|
||||||
hl.exec_cmd("sh -lc 'export IMALISON_SESSION_TYPE=wayland; dbus-update-activation-environment --systemd WAYLAND_DISPLAY DISPLAY XAUTHORITY HYPRLAND_INSTANCE_SIGNATURE XDG_CURRENT_DESKTOP XDG_SESSION_TYPE IMALISON_SESSION_TYPE; systemctl --user start graphical-session.target hyprland-session.target'")
|
apply_hyprwinview_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("hypridle")
|
hl.exec_cmd("hypridle")
|
||||||
hl.exec_cmd("wl-paste --type text --watch cliphist store")
|
hl.exec_cmd("wl-paste --type text --watch cliphist store")
|
||||||
hl.exec_cmd("wl-paste --type image --watch cliphist store")
|
hl.exec_cmd("wl-paste --type image --watch cliphist store")
|
||||||
remember_workspace_for_monitor()
|
|
||||||
write_layout_state()
|
write_layout_state()
|
||||||
schedule_nstack_count_update()
|
schedule_nstack_count_update()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
hl.on("config.reloaded", apply_nstack_config)
|
hl.on("config.reloaded", apply_nstack_config)
|
||||||
hl.on("config.reloaded", apply_hyprexpo_config)
|
hl.on("config.reloaded", apply_hyprexpo_config)
|
||||||
|
hl.on("config.reloaded", apply_hyprwinview_config)
|
||||||
|
hl.on("config.reloaded", apply_rules)
|
||||||
|
|
||||||
hl.on("window.open", schedule_nstack_count_update)
|
hl.on("window.open", schedule_nstack_count_update)
|
||||||
hl.on("window.destroy", schedule_nstack_count_update)
|
hl.on("window.destroy", schedule_nstack_count_update)
|
||||||
@@ -1483,7 +1907,6 @@ hl.on("window.kill", schedule_nstack_count_update)
|
|||||||
hl.on("window.move_to_workspace", schedule_nstack_count_update)
|
hl.on("window.move_to_workspace", schedule_nstack_count_update)
|
||||||
hl.on("workspace.active", sync_layout_for_active_workspace)
|
hl.on("workspace.active", sync_layout_for_active_workspace)
|
||||||
hl.on("monitor.focused", sync_layout_for_active_workspace)
|
hl.on("monitor.focused", sync_layout_for_active_workspace)
|
||||||
hl.on("workspace.active", remember_workspace_for_monitor)
|
|
||||||
|
|
||||||
hl.on("window.open", update_monocle_notice)
|
hl.on("window.open", update_monocle_notice)
|
||||||
hl.on("window.destroy", update_monocle_notice)
|
hl.on("window.destroy", update_monocle_notice)
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Bring window to current workspace (like XMonad's bringWindow)
|
|
||||||
# Uses rofi with icons to select a window, then moves it here.
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
||||||
source "$SCRIPT_DIR/window-icon-map.sh"
|
|
||||||
|
|
||||||
CURRENT_WS=$(hyprctl activeworkspace -j | jq -r '.id')
|
|
||||||
|
|
||||||
# Get windows on OTHER workspaces as TSV
|
|
||||||
WINDOW_DATA=$(hyprctl clients -j | jq -r --argjson cws "$CURRENT_WS" '
|
|
||||||
.[] | select(.workspace.id >= 0 and .workspace.id != $cws)
|
|
||||||
| [.address, .class, (.title | gsub("\t"; " ")), (.workspace.id | tostring)]
|
|
||||||
| @tsv')
|
|
||||||
|
|
||||||
if [ -z "$WINDOW_DATA" ]; then
|
|
||||||
notify-send "Bring Window" "No windows on other workspaces"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
addresses=()
|
|
||||||
TMPFILE=$(mktemp)
|
|
||||||
trap 'rm -f "$TMPFILE"' EXIT
|
|
||||||
|
|
||||||
while IFS=$'\t' read -r address class title ws_id; do
|
|
||||||
icon=$(icon_for_class "$class")
|
|
||||||
addresses+=("$address")
|
|
||||||
printf '%-24s %s WS:%s\0icon\x1f%s\n' \
|
|
||||||
"$class" "$title" "$ws_id" "$icon"
|
|
||||||
done <<< "$WINDOW_DATA" > "$TMPFILE"
|
|
||||||
|
|
||||||
INDEX=$(rofi -dmenu -i -show-icons -p "Bring window" -format i < "$TMPFILE") || exit 0
|
|
||||||
|
|
||||||
if [ -n "$INDEX" ] && [ -n "${addresses[$INDEX]:-}" ]; then
|
|
||||||
ADDRESS="${addresses[$INDEX]}"
|
|
||||||
hyprctl dispatch movetoworkspace "$CURRENT_WS,address:$ADDRESS"
|
|
||||||
hyprctl dispatch focuswindow "address:$ADDRESS"
|
|
||||||
fi
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Cycle between master and dwindle layouts
|
|
||||||
# Like XMonad's NextLayout
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
CURRENT=$(hyprctl getoption general:layout -j | jq -r '.str')
|
|
||||||
|
|
||||||
if [ "$CURRENT" = "master" ]; then
|
|
||||||
hyprctl keyword general:layout dwindle
|
|
||||||
notify-send "Layout" "Switched to Dwindle (binary tree)"
|
|
||||||
else
|
|
||||||
hyprctl keyword general:layout master
|
|
||||||
notify-send "Layout" "Switched to Master (XMonad-like)"
|
|
||||||
fi
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# Print an "empty" workspace id within 1..$HYPR_MAX_WORKSPACE (default 9).
|
|
||||||
#
|
|
||||||
# Preference order (lowest id wins within each tier):
|
|
||||||
# 1. Workspace exists on the target monitor and has 0 windows
|
|
||||||
# 2. Workspace id does not exist at all (will be created on dispatch)
|
|
||||||
# 3. Workspace exists (elsewhere) and has 0 windows
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# find-empty-workspace.sh [monitor] [exclude_id]
|
|
||||||
|
|
||||||
max_ws="${HYPR_MAX_WORKSPACE:-9}"
|
|
||||||
|
|
||||||
monitor="${1:-}"
|
|
||||||
exclude_id="${2:-}"
|
|
||||||
|
|
||||||
if [[ -z "${monitor}" ]]; then
|
|
||||||
monitor="$(hyprctl activeworkspace -j | jq -r '.monitor' 2>/dev/null || true)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -z "${monitor}" || "${monitor}" == "null" ]]; then
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
workspaces_json="$(hyprctl workspaces -j 2>/dev/null || echo '[]')"
|
|
||||||
|
|
||||||
unused_candidate=""
|
|
||||||
elsewhere_empty_candidate=""
|
|
||||||
|
|
||||||
for i in $(seq 1 "${max_ws}"); do
|
|
||||||
if [[ -n "${exclude_id}" && "${i}" == "${exclude_id}" ]]; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
exists="$(jq -r --argjson id "${i}" '[.[] | select(.id == $id)] | length' <<<"${workspaces_json}")"
|
|
||||||
if [[ "${exists}" == "0" ]]; then
|
|
||||||
if [[ -z "${unused_candidate}" ]]; then
|
|
||||||
unused_candidate="${i}"
|
|
||||||
fi
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
windows="$(jq -r --argjson id "${i}" '([.[] | select(.id == $id) | .windows] | .[0]) // 0' <<<"${workspaces_json}")"
|
|
||||||
if [[ "${windows}" != "0" ]]; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
ws_monitor="$(jq -r --argjson id "${i}" '([.[] | select(.id == $id) | .monitor] | .[0]) // ""' <<<"${workspaces_json}")"
|
|
||||||
if [[ "${ws_monitor}" == "${monitor}" ]]; then
|
|
||||||
printf '%s\n' "${i}"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -z "${elsewhere_empty_candidate}" ]]; then
|
|
||||||
elsewhere_empty_candidate="${i}"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ -n "${unused_candidate}" ]]; then
|
|
||||||
printf '%s\n' "${unused_candidate}"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "${elsewhere_empty_candidate}" ]]; then
|
|
||||||
printf '%s\n' "${elsewhere_empty_candidate}"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
exit 1
|
|
||||||
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Focus next window of a different class (like XMonad's focusNextClass)
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# Get focused window class
|
|
||||||
FOCUSED_CLASS=$(hyprctl activewindow -j | jq -r '.class')
|
|
||||||
FOCUSED_ADDR=$(hyprctl activewindow -j | jq -r '.address')
|
|
||||||
|
|
||||||
if [ "$FOCUSED_CLASS" = "null" ] || [ -z "$FOCUSED_CLASS" ]; then
|
|
||||||
# No focused window, just focus any window
|
|
||||||
hyprctl dispatch cyclenext
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Get all unique classes
|
|
||||||
ALL_CLASSES=$(hyprctl clients -j | jq -r '[.[] | select(.workspace.id >= 0) | .class] | unique | .[]')
|
|
||||||
|
|
||||||
# Get sorted list of classes
|
|
||||||
CLASSES_ARRAY=()
|
|
||||||
while IFS= read -r class; do
|
|
||||||
CLASSES_ARRAY+=("$class")
|
|
||||||
done <<< "$ALL_CLASSES"
|
|
||||||
|
|
||||||
# Find current class index and get next class
|
|
||||||
CURRENT_INDEX=-1
|
|
||||||
for i in "${!CLASSES_ARRAY[@]}"; do
|
|
||||||
if [ "${CLASSES_ARRAY[$i]}" = "$FOCUSED_CLASS" ]; then
|
|
||||||
CURRENT_INDEX=$i
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ $CURRENT_INDEX -eq -1 ] || [ ${#CLASSES_ARRAY[@]} -le 1 ]; then
|
|
||||||
# Only one class or class not found
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Get next class (wrapping around)
|
|
||||||
NEXT_INDEX=$(( (CURRENT_INDEX + 1) % ${#CLASSES_ARRAY[@]} ))
|
|
||||||
NEXT_CLASS="${CLASSES_ARRAY[$NEXT_INDEX]}"
|
|
||||||
|
|
||||||
# Find first window of next class
|
|
||||||
NEXT_WINDOW=$(hyprctl clients -j | jq -r ".[] | select(.class == \"$NEXT_CLASS\" and .workspace.id >= 0) | .address" | head -1)
|
|
||||||
|
|
||||||
if [ -n "$NEXT_WINDOW" ]; then
|
|
||||||
hyprctl dispatch focuswindow "address:$NEXT_WINDOW"
|
|
||||||
fi
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Gather all windows of the same class as focused window (like XMonad's gatherThisClass)
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# Get focused window class
|
|
||||||
FOCUSED_CLASS=$(hyprctl activewindow -j | jq -r '.class')
|
|
||||||
CURRENT_WS=$(hyprctl activeworkspace -j | jq -r '.id')
|
|
||||||
|
|
||||||
if [ "$FOCUSED_CLASS" = "null" ] || [ -z "$FOCUSED_CLASS" ]; then
|
|
||||||
notify-send "Gather Class" "No focused window"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Find all windows with same class on other workspaces
|
|
||||||
WINDOWS=$(hyprctl clients -j | jq -r ".[] | select(.class == \"$FOCUSED_CLASS\" and .workspace.id != $CURRENT_WS and .workspace.id >= 0) | .address")
|
|
||||||
|
|
||||||
if [ -z "$WINDOWS" ]; then
|
|
||||||
notify-send "Gather Class" "No other windows of class '$FOCUSED_CLASS'"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Move each window to current workspace
|
|
||||||
COUNT=0
|
|
||||||
for ADDR in $WINDOWS; do
|
|
||||||
hyprctl dispatch movetoworkspace "$CURRENT_WS,address:$ADDR"
|
|
||||||
COUNT=$((COUNT + 1))
|
|
||||||
done
|
|
||||||
|
|
||||||
notify-send "Gather Class" "Gathered $COUNT windows of class '$FOCUSED_CLASS'"
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Go to a window selected via rofi (with icons from desktop entries).
|
|
||||||
# Replaces "rofi -show window" which doesn't work well on Wayland.
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
||||||
source "$SCRIPT_DIR/window-icon-map.sh"
|
|
||||||
|
|
||||||
# Get all windows on regular workspaces as TSV
|
|
||||||
WINDOW_DATA=$(hyprctl clients -j | jq -r '
|
|
||||||
.[] | select(.workspace.id >= 0)
|
|
||||||
| [.address, .class, (.title | gsub("\t"; " ")), (.workspace.id | tostring)]
|
|
||||||
| @tsv')
|
|
||||||
|
|
||||||
[ -n "$WINDOW_DATA" ] || exit 0
|
|
||||||
|
|
||||||
addresses=()
|
|
||||||
TMPFILE=$(mktemp)
|
|
||||||
trap 'rm -f "$TMPFILE"' EXIT
|
|
||||||
|
|
||||||
while IFS=$'\t' read -r address class title ws_id; do
|
|
||||||
icon=$(icon_for_class "$class")
|
|
||||||
addresses+=("$address")
|
|
||||||
printf '%-24s %s WS:%s\0icon\x1f%s\n' \
|
|
||||||
"$class" "$title" "$ws_id" "$icon"
|
|
||||||
done <<< "$WINDOW_DATA" > "$TMPFILE"
|
|
||||||
|
|
||||||
INDEX=$(rofi -dmenu -i -show-icons -p "Go to window" -format i < "$TMPFILE") || exit 0
|
|
||||||
|
|
||||||
if [ -n "$INDEX" ] && [ -n "${addresses[$INDEX]:-}" ]; then
|
|
||||||
hyprctl dispatch focuswindow "address:${addresses[$INDEX]}"
|
|
||||||
fi
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Minimize the active window by moving it to a special workspace without
|
|
||||||
# toggling that special workspace open.
|
|
||||||
#
|
|
||||||
# Usage: minimize-active.sh <name>
|
|
||||||
# Example: minimize-active.sh minimized
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
NAME="${1:-minimized}"
|
|
||||||
NAME="${NAME#special:}"
|
|
||||||
|
|
||||||
if ! command -v hyprctl >/dev/null 2>&1; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
if ! command -v jq >/dev/null 2>&1; then
|
|
||||||
# We could parse plain output, but jq should exist in this setup; if it
|
|
||||||
# doesn't, fail soft.
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
ACTIVE_JSON="$(hyprctl -j activewindow 2>/dev/null || true)"
|
|
||||||
ADDR="$(printf '%s' "$ACTIVE_JSON" | jq -r '.address // empty')"
|
|
||||||
if [ -z "$ADDR" ] || [ "$ADDR" = "null" ]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# If the minimized special workspace is currently visible, closing it after the
|
|
||||||
# move keeps the window hidden (what "minimize" usually means).
|
|
||||||
MONITOR_ID="$(printf '%s' "$ACTIVE_JSON" | jq -r '.monitor // empty')"
|
|
||||||
SPECIAL_OPEN="$(
|
|
||||||
hyprctl -j monitors 2>/dev/null \
|
|
||||||
| jq -r --arg n "special:$NAME" --argjson mid "${MONITOR_ID:-0}" '
|
|
||||||
.[]
|
|
||||||
| select(.id == $mid)
|
|
||||||
| (.specialWorkspace.name // "")
|
|
||||||
| select(. == $n)
|
|
||||||
' \
|
|
||||||
| head -n 1 \
|
|
||||||
|| true
|
|
||||||
)"
|
|
||||||
|
|
||||||
hyprctl dispatch movetoworkspacesilent "special:${NAME},address:${ADDR}" >/dev/null 2>&1 || true
|
|
||||||
|
|
||||||
if [ -n "$SPECIAL_OPEN" ]; then
|
|
||||||
hyprctl dispatch togglespecialworkspace "$NAME" >/dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Exit minimized picker mode:
|
|
||||||
# - Hide the minimized special workspace on the active monitor (if visible)
|
|
||||||
# - Reset the submap
|
|
||||||
#
|
|
||||||
# Usage: minimized-cancel.sh <name>
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
NAME="${1:-minimized}"
|
|
||||||
NAME="${NAME#special:}"
|
|
||||||
SPECIAL_WS="special:${NAME}"
|
|
||||||
|
|
||||||
if ! command -v hyprctl >/dev/null 2>&1; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
if ! command -v jq >/dev/null 2>&1; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
MONITOR_ID="$(hyprctl -j activeworkspace 2>/dev/null | jq -r '.monitorID // empty' || true)"
|
|
||||||
if [ -z "$MONITOR_ID" ] || [ "$MONITOR_ID" = "null" ]; then
|
|
||||||
MONITOR_ID=0
|
|
||||||
fi
|
|
||||||
|
|
||||||
OPEN="$(
|
|
||||||
hyprctl -j monitors 2>/dev/null \
|
|
||||||
| jq -r --argjson mid "$MONITOR_ID" '.[] | select(.id == $mid) | (.specialWorkspace.name // "")' \
|
|
||||||
| head -n 1 \
|
|
||||||
|| true
|
|
||||||
)"
|
|
||||||
|
|
||||||
if [ "$OPEN" = "$SPECIAL_WS" ]; then
|
|
||||||
hyprctl dispatch togglespecialworkspace "$NAME" >/dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
hyprctl dispatch submap reset >/dev/null 2>&1 || true
|
|
||||||
exit 0
|
|
||||||
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Enter a "picker" mode for minimized windows:
|
|
||||||
# - Ensure the minimized special workspace is visible on the active monitor
|
|
||||||
# - Switch Hyprland into a submap so Enter restores and Escape cancels
|
|
||||||
#
|
|
||||||
# Usage: minimized-mode.sh <name>
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
NAME="${1:-minimized}"
|
|
||||||
NAME="${NAME#special:}"
|
|
||||||
SPECIAL_WS="special:${NAME}"
|
|
||||||
|
|
||||||
if ! command -v hyprctl >/dev/null 2>&1; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
if ! command -v jq >/dev/null 2>&1; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
MONITOR_ID="$(hyprctl -j activeworkspace 2>/dev/null | jq -r '.monitorID // empty' || true)"
|
|
||||||
if [ -z "$MONITOR_ID" ] || [ "$MONITOR_ID" = "null" ]; then
|
|
||||||
MONITOR_ID=0
|
|
||||||
fi
|
|
||||||
|
|
||||||
OPEN="$(
|
|
||||||
hyprctl -j monitors 2>/dev/null \
|
|
||||||
| jq -r --argjson mid "$MONITOR_ID" '.[] | select(.id == $mid) | (.specialWorkspace.name // "")' \
|
|
||||||
| head -n 1 \
|
|
||||||
|| true
|
|
||||||
)"
|
|
||||||
|
|
||||||
# Ensure it's visible (but don't toggle it off if already open).
|
|
||||||
if [ "$OPEN" != "$SPECIAL_WS" ]; then
|
|
||||||
hyprctl dispatch togglespecialworkspace "$NAME" >/dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
hyprctl dispatch submap minimized >/dev/null 2>&1 || true
|
|
||||||
exit 0
|
|
||||||
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Move the active window in a direction and warp the cursor to keep its
|
|
||||||
# relative position inside the moved window.
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
export PATH="/run/current-system/sw/bin:${PATH}"
|
|
||||||
|
|
||||||
if [[ $# -lt 1 ]]; then
|
|
||||||
echo "usage: $0 <dir> [mode]" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
dir="$1"
|
|
||||||
mode="${2:-}"
|
|
||||||
|
|
||||||
if ! command -v hyprctl >/dev/null; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
move_window() {
|
|
||||||
if [[ -n "$mode" ]]; then
|
|
||||||
hyprctl dispatch hy3:movewindow "$dir, $mode" >/dev/null 2>&1 || true
|
|
||||||
else
|
|
||||||
hyprctl dispatch hy3:movewindow "$dir" >/dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
win_json="$(hyprctl -j activewindow 2>/dev/null || true)"
|
|
||||||
cur_json="$(hyprctl -j cursorpos 2>/dev/null || true)"
|
|
||||||
|
|
||||||
if [[ -z "$win_json" || "$win_json" == "null" || -z "$cur_json" || "$cur_json" == "null" ]]; then
|
|
||||||
move_window
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
win_x="$(jq -er '.at[0]' <<<"$win_json" 2>/dev/null || true)"
|
|
||||||
win_y="$(jq -er '.at[1]' <<<"$win_json" 2>/dev/null || true)"
|
|
||||||
win_w="$(jq -er '.size[0]' <<<"$win_json" 2>/dev/null || true)"
|
|
||||||
win_h="$(jq -er '.size[1]' <<<"$win_json" 2>/dev/null || true)"
|
|
||||||
cur_x="$(jq -er '.x' <<<"$cur_json" 2>/dev/null || true)"
|
|
||||||
cur_y="$(jq -er '.y' <<<"$cur_json" 2>/dev/null || true)"
|
|
||||||
|
|
||||||
if [[ ! "$win_x" =~ ^-?[0-9]+$ || ! "$win_y" =~ ^-?[0-9]+$ || ! "$win_w" =~ ^-?[0-9]+$ || ! "$win_h" =~ ^-?[0-9]+$ || ! "$cur_x" =~ ^-?[0-9]+$ || ! "$cur_y" =~ ^-?[0-9]+$ ]]; then
|
|
||||||
move_window
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
rel_x=$((cur_x - win_x))
|
|
||||||
rel_y=$((cur_y - win_y))
|
|
||||||
|
|
||||||
move_window
|
|
||||||
|
|
||||||
win_json="$(hyprctl -j activewindow 2>/dev/null || true)"
|
|
||||||
if [[ -z "$win_json" || "$win_json" == "null" ]]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
win_x="$(jq -er '.at[0]' <<<"$win_json" 2>/dev/null || true)"
|
|
||||||
win_y="$(jq -er '.at[1]' <<<"$win_json" 2>/dev/null || true)"
|
|
||||||
win_w="$(jq -er '.size[0]' <<<"$win_json" 2>/dev/null || true)"
|
|
||||||
win_h="$(jq -er '.size[1]' <<<"$win_json" 2>/dev/null || true)"
|
|
||||||
|
|
||||||
if [[ ! "$win_x" =~ ^-?[0-9]+$ || ! "$win_y" =~ ^-?[0-9]+$ || ! "$win_w" =~ ^-?[0-9]+$ || ! "$win_h" =~ ^-?[0-9]+$ ]]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ((rel_x < 0)); then
|
|
||||||
rel_x=0
|
|
||||||
elif ((rel_x > win_w)); then
|
|
||||||
rel_x=$win_w
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ((rel_y < 0)); then
|
|
||||||
rel_y=0
|
|
||||||
elif ((rel_y > win_h)); then
|
|
||||||
rel_y=$win_h
|
|
||||||
fi
|
|
||||||
|
|
||||||
new_x=$((win_x + rel_x))
|
|
||||||
new_y=$((win_y + rel_y))
|
|
||||||
|
|
||||||
hyprctl dispatch movecursor "$new_x" "$new_y" >/dev/null 2>&1 || true
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Raise existing window or run command (like XMonad's raiseNextMaybe)
|
|
||||||
# Usage: raise-or-run.sh <class-pattern> <command>
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
CLASS_PATTERN="$1"
|
|
||||||
COMMAND="$2"
|
|
||||||
|
|
||||||
# Find windows matching the class pattern
|
|
||||||
MATCHING=$(hyprctl clients -j | jq -r ".[] | select(.class | test(\"$CLASS_PATTERN\"; \"i\")) | .address" | head -1)
|
|
||||||
|
|
||||||
if [ -n "$MATCHING" ]; then
|
|
||||||
# Window exists, focus it
|
|
||||||
hyprctl dispatch focuswindow "address:$MATCHING"
|
|
||||||
else
|
|
||||||
# No matching window, run the command
|
|
||||||
exec $COMMAND
|
|
||||||
fi
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Replace focused window with selected window (like XMonad's myReplaceWindow)
|
|
||||||
# Swaps the positions of focused window and selected window
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
||||||
source "$SCRIPT_DIR/window-icon-map.sh"
|
|
||||||
|
|
||||||
FOCUSED=$(hyprctl activewindow -j | jq -r '.address')
|
|
||||||
|
|
||||||
if [ "$FOCUSED" = "null" ] || [ -z "$FOCUSED" ]; then
|
|
||||||
notify-send "Replace Window" "No focused window"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Get all windows except focused as TSV
|
|
||||||
WINDOW_DATA=$(hyprctl clients -j | jq -r --arg focused "$FOCUSED" '
|
|
||||||
.[] | select(.workspace.id >= 0 and .address != $focused)
|
|
||||||
| [.address, .class, (.title | gsub("\t"; " ")), (.workspace.id | tostring)]
|
|
||||||
| @tsv')
|
|
||||||
|
|
||||||
if [ -z "$WINDOW_DATA" ]; then
|
|
||||||
notify-send "Replace Window" "No other windows available"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
addresses=()
|
|
||||||
TMPFILE=$(mktemp)
|
|
||||||
trap 'rm -f "$TMPFILE"' EXIT
|
|
||||||
|
|
||||||
while IFS=$'\t' read -r address class title ws_id; do
|
|
||||||
icon=$(icon_for_class "$class")
|
|
||||||
addresses+=("$address")
|
|
||||||
printf '%-24s %s WS:%s\0icon\x1f%s\n' \
|
|
||||||
"$class" "$title" "$ws_id" "$icon"
|
|
||||||
done <<< "$WINDOW_DATA" > "$TMPFILE"
|
|
||||||
|
|
||||||
INDEX=$(rofi -dmenu -i -show-icons -p "Replace with" -format i < "$TMPFILE") || exit 0
|
|
||||||
|
|
||||||
if [ -n "$INDEX" ] && [ -n "${addresses[$INDEX]:-}" ]; then
|
|
||||||
hyprctl dispatch hy3:movewindow "address:${addresses[$INDEX]}"
|
|
||||||
fi
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Shift window to empty workspace on screen in given direction
|
|
||||||
# Like XMonad's shiftToEmptyOnScreen
|
|
||||||
# Usage: shift-to-empty-on-screen.sh <direction: u|d|l|r>
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
DIRECTION="$1"
|
|
||||||
max_ws="${HYPR_MAX_WORKSPACE:-9}"
|
|
||||||
|
|
||||||
# Track the current monitor so we can return
|
|
||||||
ORIG_MONITOR=$(hyprctl activeworkspace -j | jq -r '.monitor')
|
|
||||||
|
|
||||||
# Move focus to the screen in that direction
|
|
||||||
hyprctl dispatch focusmonitor "$DIRECTION"
|
|
||||||
|
|
||||||
# Get the monitor we're now on (target monitor)
|
|
||||||
MONITOR=$(hyprctl activeworkspace -j | jq -r '.monitor')
|
|
||||||
|
|
||||||
# If there is no monitor in that direction, bail
|
|
||||||
if [ "$MONITOR" = "$ORIG_MONITOR" ]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Find an empty workspace within 1..$HYPR_MAX_WORKSPACE.
|
|
||||||
EMPTY_WS="$(~/.config/hypr/scripts/find-empty-workspace.sh "${MONITOR}" 2>/dev/null || true)"
|
|
||||||
if [[ -z "${EMPTY_WS}" ]]; then
|
|
||||||
# No empty workspace available within the cap; restore focus and bail.
|
|
||||||
hyprctl dispatch focusmonitor "$ORIG_MONITOR"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if (( EMPTY_WS < 1 || EMPTY_WS > max_ws )); then
|
|
||||||
hyprctl dispatch focusmonitor "$ORIG_MONITOR"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Ensure the workspace exists on the target monitor
|
|
||||||
hyprctl dispatch workspace "$EMPTY_WS"
|
|
||||||
|
|
||||||
# Go back to original monitor and move the window (without following)
|
|
||||||
hyprctl dispatch focusmonitor "$ORIG_MONITOR"
|
|
||||||
hyprctl dispatch movetoworkspacesilent "$EMPTY_WS"
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Swap the contents of the current workspace with another workspace.
|
|
||||||
# Intended to mirror XMonad's swapWithCurrent behavior.
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
max_ws="${HYPR_MAX_WORKSPACE:-9}"
|
|
||||||
|
|
||||||
CURRENT_WS="$(hyprctl activeworkspace -j | jq -r '.id')"
|
|
||||||
if [[ -z "${CURRENT_WS}" || "${CURRENT_WS}" == "null" ]]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
TARGET_WS="${1:-}"
|
|
||||||
|
|
||||||
if [[ -z "${TARGET_WS}" ]]; then
|
|
||||||
WS_LIST="$({
|
|
||||||
seq 1 "${max_ws}"
|
|
||||||
hyprctl workspaces -j | jq -r '.[].id' 2>/dev/null || true
|
|
||||||
} | awk 'NF {print $1}' | awk '!seen[$0]++' | sort -n)"
|
|
||||||
|
|
||||||
TARGET_WS="$(printf "%s\n" "${WS_LIST}" | rofi -dmenu -p "Swap with workspace")"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -z "${TARGET_WS}" || "${TARGET_WS}" == "null" ]]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${TARGET_WS}" == "${CURRENT_WS}" ]]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! [[ "${TARGET_WS}" =~ ^-?[0-9]+$ ]]; then
|
|
||||||
notify-send "Swap Workspace" "Invalid workspace: ${TARGET_WS}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if (( TARGET_WS < 1 || TARGET_WS > max_ws )); then
|
|
||||||
notify-send "Swap Workspace" "Workspace out of range (1-${max_ws}): ${TARGET_WS}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
WINDOWS_CURRENT="$(hyprctl clients -j | jq -r --arg ws "${CURRENT_WS}" '.[] | select((.workspace.id|tostring) == $ws) | .address')"
|
|
||||||
WINDOWS_TARGET="$(hyprctl clients -j | jq -r --arg ws "${TARGET_WS}" '.[] | select((.workspace.id|tostring) == $ws) | .address')"
|
|
||||||
|
|
||||||
for ADDR in ${WINDOWS_CURRENT}; do
|
|
||||||
hyprctl dispatch movetoworkspace "${TARGET_WS},address:${ADDR}"
|
|
||||||
done
|
|
||||||
|
|
||||||
for ADDR in ${WINDOWS_TARGET}; do
|
|
||||||
hyprctl dispatch movetoworkspace "${CURRENT_WS},address:${ADDR}"
|
|
||||||
done
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Toggle a named Hyprland scratchpad, spawning it if needed.
|
|
||||||
# Usage: toggle-scratchpad.sh <name> <class_regex|-> <title_regex|-> <command...>
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
if [ "$#" -lt 4 ]; then
|
|
||||||
echo "usage: $0 <name> <class_regex|-> <title_regex|-> <command...>" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
NAME="$1"
|
|
||||||
shift
|
|
||||||
CLASS_REGEX="$1"
|
|
||||||
shift
|
|
||||||
TITLE_REGEX="$1"
|
|
||||||
shift
|
|
||||||
COMMAND=("$@")
|
|
||||||
|
|
||||||
if [ "$CLASS_REGEX" = "-" ]; then
|
|
||||||
CLASS_REGEX=""
|
|
||||||
fi
|
|
||||||
if [ "$TITLE_REGEX" = "-" ]; then
|
|
||||||
TITLE_REGEX=""
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$CLASS_REGEX" ] && [ -z "$TITLE_REGEX" ]; then
|
|
||||||
echo "toggle-scratchpad: provide a class or title regex" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
MATCHING=$(hyprctl clients -j | jq -r --arg cre "$CLASS_REGEX" --arg tre "$TITLE_REGEX" '
|
|
||||||
.[]
|
|
||||||
| select(
|
|
||||||
(($cre == "") or (.class | test($cre; "i")))
|
|
||||||
and
|
|
||||||
(($tre == "") or (.title | test($tre; "i")))
|
|
||||||
)
|
|
||||||
| .address
|
|
||||||
')
|
|
||||||
|
|
||||||
if [ -z "$MATCHING" ]; then
|
|
||||||
"${COMMAND[@]}" &
|
|
||||||
else
|
|
||||||
while IFS= read -r ADDR; do
|
|
||||||
[ -n "$ADDR" ] || continue
|
|
||||||
hyprctl dispatch movetoworkspacesilent "special:$NAME,address:$ADDR"
|
|
||||||
done <<< "$MATCHING"
|
|
||||||
fi
|
|
||||||
|
|
||||||
hyprctl dispatch togglespecialworkspace "$NAME"
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Restore a minimized window by moving it out of a special workspace.
|
|
||||||
#
|
|
||||||
# Usage: unminimize-last.sh <name>
|
|
||||||
# Example: unminimize-last.sh minimized
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
NAME="${1:-minimized}"
|
|
||||||
NAME="${NAME#special:}"
|
|
||||||
SPECIAL_WS="special:${NAME}"
|
|
||||||
|
|
||||||
if ! command -v hyprctl >/dev/null 2>&1; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
if ! command -v jq >/dev/null 2>&1; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
ACTIVE_JSON="$(hyprctl -j activewindow 2>/dev/null || true)"
|
|
||||||
ACTIVE_ADDR="$(printf '%s' "$ACTIVE_JSON" | jq -r '.address // empty')"
|
|
||||||
ACTIVE_WS="$(printf '%s' "$ACTIVE_JSON" | jq -r '.workspace.name // empty')"
|
|
||||||
MONITOR_ID="$(printf '%s' "$ACTIVE_JSON" | jq -r '.monitor // empty')"
|
|
||||||
|
|
||||||
# Destination is the normal active workspace for the active monitor.
|
|
||||||
DEST_WS="$(
|
|
||||||
hyprctl -j monitors 2>/dev/null \
|
|
||||||
| jq -r --argjson mid "${MONITOR_ID:-0}" '.[] | select(.id == $mid) | .activeWorkspace.name' \
|
|
||||||
| head -n 1 \
|
|
||||||
|| true
|
|
||||||
)"
|
|
||||||
if [ -z "$DEST_WS" ] || [ "$DEST_WS" = "null" ]; then
|
|
||||||
DEST_WS="$(hyprctl -j activeworkspace 2>/dev/null | jq -r '.name // empty' || true)"
|
|
||||||
fi
|
|
||||||
if [ -z "$DEST_WS" ] || [ "$DEST_WS" = "null" ]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# If we're focused on a minimized window already, restore that one.
|
|
||||||
ADDR=""
|
|
||||||
if [ "$ACTIVE_WS" = "$SPECIAL_WS" ] && [ -n "$ACTIVE_ADDR" ] && [ "$ACTIVE_ADDR" != "null" ]; then
|
|
||||||
ADDR="$ACTIVE_ADDR"
|
|
||||||
else
|
|
||||||
# Otherwise, restore the "most recent" minimized window we can find.
|
|
||||||
# focusHistoryID tends to have 0 as most recent; pick the smallest value.
|
|
||||||
ADDR="$(
|
|
||||||
hyprctl -j clients 2>/dev/null \
|
|
||||||
| jq -r --arg sw "$SPECIAL_WS" '
|
|
||||||
[ .[]
|
|
||||||
| select(.workspace.name == $sw)
|
|
||||||
| { addr: .address, fh: (.focusHistoryID // 999999999) }
|
|
||||||
]
|
|
||||||
| sort_by(.fh)
|
|
||||||
| (.[0].addr // empty)
|
|
||||||
' \
|
|
||||||
| head -n 1 \
|
|
||||||
|| true
|
|
||||||
)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$ADDR" ] || [ "$ADDR" = "null" ]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
hyprctl dispatch movetoworkspacesilent "${DEST_WS},address:${ADDR}" >/dev/null 2>&1 || true
|
|
||||||
hyprctl dispatch focuswindow "address:${ADDR}" >/dev/null 2>&1 || true
|
|
||||||
|
|
||||||
# If the minimized special workspace is currently visible, close it so we don't
|
|
||||||
# leave things in a special state after a restore.
|
|
||||||
SPECIAL_OPEN="$(
|
|
||||||
hyprctl -j monitors 2>/dev/null \
|
|
||||||
| jq -r --arg n "$SPECIAL_WS" --argjson mid "${MONITOR_ID:-0}" '
|
|
||||||
.[]
|
|
||||||
| select(.id == $mid)
|
|
||||||
| (.specialWorkspace.name // "")
|
|
||||||
| select(. == $n)
|
|
||||||
' \
|
|
||||||
| head -n 1 \
|
|
||||||
|| true
|
|
||||||
)"
|
|
||||||
if [ -n "$SPECIAL_OPEN" ]; then
|
|
||||||
hyprctl dispatch togglespecialworkspace "$NAME" >/dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Source this file to get icon_for_class function.
|
|
||||||
# Builds a mapping from window class → freedesktop icon name
|
|
||||||
# by scanning .desktop files for StartupWMClass and Icon fields.
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# source "$(dirname "$0")/window-icon-map.sh"
|
|
||||||
# icon=$(icon_for_class "google-chrome")
|
|
||||||
|
|
||||||
declare -A _WINDOW_ICON_MAP
|
|
||||||
|
|
||||||
_build_window_icon_map() {
|
|
||||||
local IFS=':'
|
|
||||||
local -a search_dirs=()
|
|
||||||
local dir
|
|
||||||
|
|
||||||
for dir in ${XDG_DATA_DIRS:-/run/current-system/sw/share:/usr/share:/usr/local/share}; do
|
|
||||||
[ -d "$dir/applications" ] && search_dirs+=("$dir/applications")
|
|
||||||
done
|
|
||||||
[ -d "$HOME/.local/share/applications" ] && search_dirs+=("$HOME/.local/share/applications")
|
|
||||||
[ ${#search_dirs[@]} -eq 0 ] && return
|
|
||||||
|
|
||||||
# Expand globs per-directory so the pattern works correctly
|
|
||||||
local -a desktop_files=()
|
|
||||||
for dir in "${search_dirs[@]}"; do
|
|
||||||
desktop_files+=("$dir"/*.desktop)
|
|
||||||
done
|
|
||||||
[ ${#desktop_files[@]} -eq 0 ] && return
|
|
||||||
|
|
||||||
# Single grep pass across all desktop files
|
|
||||||
local -A file_icons file_wmclass
|
|
||||||
local filepath line
|
|
||||||
while IFS=: read -r filepath line; do
|
|
||||||
case "$line" in
|
|
||||||
Icon=*)
|
|
||||||
[ -z "${file_icons[$filepath]:-}" ] && file_icons["$filepath"]="${line#Icon=}"
|
|
||||||
;;
|
|
||||||
StartupWMClass=*)
|
|
||||||
[ -z "${file_wmclass[$filepath]:-}" ] && file_wmclass["$filepath"]="${line#StartupWMClass=}"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done < <(grep -H '^Icon=\|^StartupWMClass=' "${desktop_files[@]}" 2>/dev/null)
|
|
||||||
|
|
||||||
# Build class → icon map
|
|
||||||
local icon wm_class bn name
|
|
||||||
for filepath in "${!file_icons[@]}"; do
|
|
||||||
icon="${file_icons[$filepath]}"
|
|
||||||
[ -n "$icon" ] || continue
|
|
||||||
|
|
||||||
wm_class="${file_wmclass[$filepath]:-}"
|
|
||||||
if [ -n "$wm_class" ]; then
|
|
||||||
_WINDOW_ICON_MAP["${wm_class,,}"]="$icon"
|
|
||||||
fi
|
|
||||||
|
|
||||||
bn="${filepath##*/}"
|
|
||||||
name="${bn%.desktop}"
|
|
||||||
_WINDOW_ICON_MAP["${name,,}"]="$icon"
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
_build_window_icon_map
|
|
||||||
|
|
||||||
icon_for_class() {
|
|
||||||
local class_lower="${1,,}"
|
|
||||||
echo "${_WINDOW_ICON_MAP[$class_lower]:-$class_lower}"
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
cur_ws="$(hyprctl activeworkspace -j | jq -r '.id' 2>/dev/null || true)"
|
|
||||||
monitor="$(hyprctl activeworkspace -j | jq -r '.monitor' 2>/dev/null || true)"
|
|
||||||
|
|
||||||
ws="$(
|
|
||||||
~/.config/hypr/scripts/find-empty-workspace.sh "${monitor}" "${cur_ws}" 2>/dev/null || true
|
|
||||||
)"
|
|
||||||
|
|
||||||
if [[ -z "${ws}" ]]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
hyprctl dispatch workspace "${ws}" >/dev/null 2>&1 || true
|
|
||||||
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
cur_ws="$(hyprctl activeworkspace -j | jq -r '.id' 2>/dev/null || true)"
|
|
||||||
monitor="$(hyprctl activeworkspace -j | jq -r '.monitor' 2>/dev/null || true)"
|
|
||||||
|
|
||||||
ws="$(
|
|
||||||
~/.config/hypr/scripts/find-empty-workspace.sh "${monitor}" "${cur_ws}" 2>/dev/null || true
|
|
||||||
)"
|
|
||||||
|
|
||||||
if [[ -z "${ws}" ]]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
hyprctl dispatch movetoworkspace "${ws}" >/dev/null 2>&1 || true
|
|
||||||
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
max_ws="${HYPR_MAX_WORKSPACE:-9}"
|
|
||||||
delta="${1:-}"
|
|
||||||
|
|
||||||
case "${delta}" in
|
|
||||||
+1|-1) ;;
|
|
||||||
next) delta="+1" ;;
|
|
||||||
prev) delta="-1" ;;
|
|
||||||
*)
|
|
||||||
exit 2
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
cur="$(hyprctl activeworkspace -j | jq -r '.id' 2>/dev/null || true)"
|
|
||||||
if ! [[ "${cur}" =~ ^[0-9]+$ ]]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if (( cur < 1 )); then
|
|
||||||
cur=1
|
|
||||||
elif (( cur > max_ws )); then
|
|
||||||
cur="${max_ws}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${delta}" == "+1" ]]; then
|
|
||||||
if (( cur >= max_ws )); then
|
|
||||||
nxt=1
|
|
||||||
else
|
|
||||||
nxt=$((cur + 1))
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
if (( cur <= 1 )); then
|
|
||||||
nxt="${max_ws}"
|
|
||||||
else
|
|
||||||
nxt=$((cur - 1))
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
hyprctl dispatch workspace "${nxt}" >/dev/null 2>&1 || true
|
|
||||||
|
|
||||||
23
dotfiles/config/hypr/workspace-history-plugin/CMakeLists.txt
Normal file
23
dotfiles/config/hypr/workspace-history-plugin/CMakeLists.txt
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.27)
|
||||||
|
|
||||||
|
project(hypr-workspace-history
|
||||||
|
DESCRIPTION "Workspace history cycling plugin for Hyprland"
|
||||||
|
VERSION 0.1.0
|
||||||
|
LANGUAGES CXX
|
||||||
|
)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
add_library(hypr-workspace-history SHARED src/main.cpp)
|
||||||
|
|
||||||
|
find_package(PkgConfig REQUIRED)
|
||||||
|
pkg_check_modules(deps REQUIRED IMPORTED_TARGET
|
||||||
|
hyprland
|
||||||
|
wayland-server
|
||||||
|
xkbcommon
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(hypr-workspace-history PRIVATE rt PkgConfig::deps)
|
||||||
|
|
||||||
|
install(TARGETS hypr-workspace-history)
|
||||||
406
dotfiles/config/hypr/workspace-history-plugin/src/main.cpp
Normal file
406
dotfiles/config/hypr/workspace-history-plugin/src/main.cpp
Normal file
@@ -0,0 +1,406 @@
|
|||||||
|
#define WLR_USE_UNSTABLE
|
||||||
|
|
||||||
|
#include <hyprland/src/Compositor.hpp>
|
||||||
|
#include <hyprland/src/debug/log/Logger.hpp>
|
||||||
|
#include <hyprland/src/desktop/Workspace.hpp>
|
||||||
|
#include <hyprland/src/desktop/state/FocusState.hpp>
|
||||||
|
#include <hyprland/src/devices/IKeyboard.hpp>
|
||||||
|
#include <hyprland/src/event/EventBus.hpp>
|
||||||
|
#include <hyprland/src/helpers/Monitor.hpp>
|
||||||
|
#include <hyprland/src/managers/KeybindManager.hpp>
|
||||||
|
#include <hyprland/src/plugins/PluginAPI.hpp>
|
||||||
|
|
||||||
|
#include <lua.hpp>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <ctime>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <map>
|
||||||
|
#include <optional>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
inline HANDLE PHANDLE = nullptr;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr int MAX_WORKSPACE = 9;
|
||||||
|
|
||||||
|
struct SCycleState {
|
||||||
|
std::string monitorKey;
|
||||||
|
int originalWorkspace = 0;
|
||||||
|
int previewWorkspace = 0;
|
||||||
|
std::vector<int> history;
|
||||||
|
size_t nextIndex = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CWorkspaceHistory {
|
||||||
|
public:
|
||||||
|
void seedActiveWorkspaces() {
|
||||||
|
if (!g_pCompositor)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (const auto& monitor : g_pCompositor->m_monitors) {
|
||||||
|
if (monitor && monitor->m_activeWorkspace)
|
||||||
|
remember(monitor->m_activeWorkspace);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeDebug("seed");
|
||||||
|
}
|
||||||
|
|
||||||
|
void observe(PHLWORKSPACE workspace) {
|
||||||
|
const auto workspaceId = workspaceID(workspace);
|
||||||
|
if (!workspaceId) {
|
||||||
|
writeDebug("observe-skipped-non-normal-workspace");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto key = monitorKey(workspace);
|
||||||
|
if (!m_cycle || m_cycle->monitorKey != key) {
|
||||||
|
remember(workspace);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contains(m_cycle->history, *workspaceId)) {
|
||||||
|
m_cycle->previewWorkspace = *workspaceId;
|
||||||
|
writeDebug("cycle-observe-preview");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_cycle.reset();
|
||||||
|
remember(workspace);
|
||||||
|
writeDebug("remember-after-cycle-abandoned");
|
||||||
|
}
|
||||||
|
|
||||||
|
SDispatchResult cycle(int direction) {
|
||||||
|
auto* cycle = startCycle();
|
||||||
|
if (!cycle)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
if (cycle->history.size() < 2) {
|
||||||
|
writeDebug("cycle-skipped-short-history");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto target = cycle->history[cycle->nextIndex];
|
||||||
|
cycle->previewWorkspace = target;
|
||||||
|
const auto result = focusWorkspace(target);
|
||||||
|
if (!result.success) {
|
||||||
|
writeDebug("cycle-focus-failed");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
cycle->nextIndex = wrappedIndex(cycle->nextIndex, direction, cycle->history.size());
|
||||||
|
writeDebug(std::string("cycle-preview-") + std::to_string(target));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
SDispatchResult commit() {
|
||||||
|
if (!m_cycle) {
|
||||||
|
writeDebug("commit-skipped-no-cycle");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto cycle = *m_cycle;
|
||||||
|
m_cycle.reset();
|
||||||
|
m_histories[cycle.monitorKey] = promote(cycle.history, cycle.previewWorkspace);
|
||||||
|
writeDebug("commit");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
SDispatchResult cancel() {
|
||||||
|
if (!m_cycle) {
|
||||||
|
writeDebug("cancel-skipped-no-cycle");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto original = m_cycle->originalWorkspace;
|
||||||
|
m_cycle.reset();
|
||||||
|
focusWorkspace(original);
|
||||||
|
writeDebug("cancel");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void onKey(IKeyboard::SKeyEvent event) {
|
||||||
|
if (!m_cycle || event.state != WL_KEYBOARD_KEY_STATE_RELEASED || !g_pKeybindManager)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (g_pKeybindManager->keycodeToModifier(event.keycode + 8) == HL_MODIFIER_META)
|
||||||
|
commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string snapshot(const std::string& reason) const {
|
||||||
|
std::stringstream out;
|
||||||
|
out << "reason=" << reason << "\n";
|
||||||
|
|
||||||
|
const auto monitor = Desktop::focusState() ? Desktop::focusState()->monitor() : nullptr;
|
||||||
|
const auto workspace = monitor ? monitor->m_activeWorkspace : nullptr;
|
||||||
|
out << "active_monitor=" << monitorKey(monitor) << "\n";
|
||||||
|
out << "active_workspace=" << (workspace ? std::to_string(workspace->m_id) : "?") << "\n";
|
||||||
|
|
||||||
|
for (const auto& [key, history] : m_histories) {
|
||||||
|
out << "history." << key << "=";
|
||||||
|
for (size_t i = 0; i < history.size(); ++i) {
|
||||||
|
if (i > 0)
|
||||||
|
out << ",";
|
||||||
|
out << history[i];
|
||||||
|
}
|
||||||
|
out << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_cycle) {
|
||||||
|
out << "cycle.monitor=" << m_cycle->monitorKey << "\n";
|
||||||
|
out << "cycle.original=" << m_cycle->originalWorkspace << "\n";
|
||||||
|
out << "cycle.preview=" << m_cycle->previewWorkspace << "\n";
|
||||||
|
out << "cycle.next_index=" << (m_cycle->nextIndex + 1) << "\n";
|
||||||
|
out << "cycle.history=";
|
||||||
|
for (size_t i = 0; i < m_cycle->history.size(); ++i) {
|
||||||
|
if (i > 0)
|
||||||
|
out << ",";
|
||||||
|
out << m_cycle->history[i];
|
||||||
|
}
|
||||||
|
out << "\n";
|
||||||
|
} else {
|
||||||
|
out << "cycle=none\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return out.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
void showDebug() const {
|
||||||
|
HyprlandAPI::addNotification(PHANDLE, snapshot("notification"), CHyprColor{0.4, 0.8, 1.0, 1.0}, 6000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::map<std::string, std::vector<int>> m_histories;
|
||||||
|
std::optional<SCycleState> m_cycle;
|
||||||
|
|
||||||
|
static std::optional<int> workspaceID(PHLWORKSPACE workspace) {
|
||||||
|
if (!workspace || workspace->m_id < 1 || workspace->m_id > MAX_WORKSPACE)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
return workspace->m_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string monitorKey(PHLMONITOR monitor) {
|
||||||
|
if (!monitor)
|
||||||
|
return "unknown";
|
||||||
|
|
||||||
|
if (!monitor->m_name.empty())
|
||||||
|
return monitor->m_name;
|
||||||
|
|
||||||
|
return std::to_string(monitor->m_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string monitorKey(PHLWORKSPACE workspace) {
|
||||||
|
if (!workspace || !workspace->m_monitor)
|
||||||
|
return monitorKey(Desktop::focusState() ? Desktop::focusState()->monitor() : nullptr);
|
||||||
|
|
||||||
|
return monitorKey(workspace->m_monitor.lock());
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool contains(const std::vector<int>& history, int workspace) {
|
||||||
|
return std::ranges::contains(history, workspace);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<int> promote(std::vector<int> history, int workspace) {
|
||||||
|
std::erase(history, workspace);
|
||||||
|
history.insert(history.begin(), workspace);
|
||||||
|
return history;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t wrappedIndex(size_t current, int direction, size_t size) {
|
||||||
|
const auto signedSize = static_cast<int>(size);
|
||||||
|
auto next = static_cast<int>(current) + direction;
|
||||||
|
next = ((next % signedSize) + signedSize) % signedSize;
|
||||||
|
return static_cast<size_t>(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
PHLWORKSPACE activeWorkspace() const {
|
||||||
|
const auto monitor = Desktop::focusState() ? Desktop::focusState()->monitor() : nullptr;
|
||||||
|
return monitor ? monitor->m_activeWorkspace : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void remember(PHLWORKSPACE workspace) {
|
||||||
|
const auto workspaceId = workspaceID(workspace);
|
||||||
|
if (!workspaceId) {
|
||||||
|
writeDebug("remember-skipped-non-normal-workspace");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto key = monitorKey(workspace);
|
||||||
|
auto& history = m_histories[key];
|
||||||
|
const bool changed = history.empty() || history.front() != *workspaceId;
|
||||||
|
history = promote(history, *workspaceId);
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
writeDebug("remember");
|
||||||
|
}
|
||||||
|
|
||||||
|
SCycleState* startCycle() {
|
||||||
|
if (m_cycle)
|
||||||
|
return &*m_cycle;
|
||||||
|
|
||||||
|
const auto workspace = activeWorkspace();
|
||||||
|
remember(workspace);
|
||||||
|
|
||||||
|
const auto workspaceId = workspaceID(workspace);
|
||||||
|
if (!workspaceId) {
|
||||||
|
writeDebug("cycle-start-skipped-non-normal-workspace");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto key = monitorKey(workspace);
|
||||||
|
auto history = promote(m_histories[key], *workspaceId);
|
||||||
|
|
||||||
|
if (history.size() < 2) {
|
||||||
|
writeDebug("cycle-start-skipped-short-history");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_cycle = SCycleState{
|
||||||
|
.monitorKey = key,
|
||||||
|
.originalWorkspace = *workspaceId,
|
||||||
|
.previewWorkspace = *workspaceId,
|
||||||
|
.history = history,
|
||||||
|
.nextIndex = 1,
|
||||||
|
};
|
||||||
|
writeDebug("cycle-start");
|
||||||
|
return &*m_cycle;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SDispatchResult focusWorkspace(int workspace) {
|
||||||
|
if (!g_pKeybindManager)
|
||||||
|
return {.success = false, .error = "keybind manager is unavailable"};
|
||||||
|
|
||||||
|
const auto dispatcher = g_pKeybindManager->m_dispatchers.find("focusworkspaceoncurrentmonitor");
|
||||||
|
if (dispatcher == g_pKeybindManager->m_dispatchers.end())
|
||||||
|
return {.success = false, .error = "focusworkspaceoncurrentmonitor dispatcher is unavailable"};
|
||||||
|
|
||||||
|
return dispatcher->second(std::to_string(workspace));
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::optional<std::string> runtimePath(const std::string& name) {
|
||||||
|
const auto runtimeDir = std::getenv("XDG_RUNTIME_DIR");
|
||||||
|
if (!runtimeDir)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
return std::string(runtimeDir) + "/" + name;
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeDebug(const std::string& reason) const {
|
||||||
|
const auto statePath = runtimePath("hyprland-workspace-history-state");
|
||||||
|
const auto logPath = runtimePath("hyprland-workspace-history.log");
|
||||||
|
if (!statePath || !logPath)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto body = snapshot(reason);
|
||||||
|
std::ofstream state(*statePath, std::ios::trunc);
|
||||||
|
if (state)
|
||||||
|
state << body;
|
||||||
|
|
||||||
|
std::ofstream log(*logPath, std::ios::app);
|
||||||
|
if (log) {
|
||||||
|
const auto now = std::time(nullptr);
|
||||||
|
log << "--- " << std::put_time(std::localtime(&now), "%Y-%m-%d %H:%M:%S") << " ---\n";
|
||||||
|
log << body;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
CWorkspaceHistory g_workspaceHistory;
|
||||||
|
|
||||||
|
SDispatchResult dispatchCycle(std::string arg) {
|
||||||
|
int direction = 1;
|
||||||
|
if (arg == "-1" || arg == "previous" || arg == "prev" || arg == "reverse")
|
||||||
|
direction = -1;
|
||||||
|
return g_workspaceHistory.cycle(direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
SDispatchResult dispatchCommit(std::string) {
|
||||||
|
return g_workspaceHistory.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
SDispatchResult dispatchCancel(std::string) {
|
||||||
|
return g_workspaceHistory.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
SDispatchResult dispatchDebug(std::string) {
|
||||||
|
g_workspaceHistory.showDebug();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
int luaCycle(lua_State* L) {
|
||||||
|
const auto result = g_workspaceHistory.cycle(static_cast<int>(luaL_optinteger(L, 1, 1)));
|
||||||
|
if (!result.success)
|
||||||
|
return luaL_error(L, "%s", result.error.c_str());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int luaCommit(lua_State* L) {
|
||||||
|
const auto result = g_workspaceHistory.commit();
|
||||||
|
if (!result.success)
|
||||||
|
return luaL_error(L, "%s", result.error.c_str());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int luaCancel(lua_State* L) {
|
||||||
|
const auto result = g_workspaceHistory.cancel();
|
||||||
|
if (!result.success)
|
||||||
|
return luaL_error(L, "%s", result.error.c_str());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int luaDebug(lua_State*) {
|
||||||
|
g_workspaceHistory.showDebug();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void failNotification(const std::string& reason) {
|
||||||
|
HyprlandAPI::addNotification(PHANDLE, "[workspace-history] " + reason, CHyprColor{1.0, 0.2, 0.2, 1.0}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
APICALL EXPORT std::string PLUGIN_API_VERSION() {
|
||||||
|
return HYPRLAND_API_VERSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
|
||||||
|
PHANDLE = handle;
|
||||||
|
|
||||||
|
const std::string hash = __hyprland_api_get_hash();
|
||||||
|
const std::string clientHash = __hyprland_api_get_client_hash();
|
||||||
|
if (hash != clientHash) {
|
||||||
|
failNotification("version mismatch between Hyprland headers and running Hyprland");
|
||||||
|
throw std::runtime_error("[workspace-history] version mismatch");
|
||||||
|
}
|
||||||
|
|
||||||
|
static auto workspaceHook = Event::bus()->m_events.workspace.active.listen([](PHLWORKSPACE workspace) { g_workspaceHistory.observe(workspace); });
|
||||||
|
static auto keyboardHook = Event::bus()->m_events.input.keyboard.key.listen([](IKeyboard::SKeyEvent event, Event::SCallbackInfo&) { g_workspaceHistory.onKey(event); });
|
||||||
|
static auto startHook = Event::bus()->m_events.start.listen([] { g_workspaceHistory.seedActiveWorkspaces(); });
|
||||||
|
static auto reloadHook = Event::bus()->m_events.config.reloaded.listen([] { g_workspaceHistory.seedActiveWorkspaces(); });
|
||||||
|
static auto monitorHook = Event::bus()->m_events.monitor.focused.listen([](PHLMONITOR monitor) {
|
||||||
|
if (monitor && monitor->m_activeWorkspace)
|
||||||
|
g_workspaceHistory.observe(monitor->m_activeWorkspace);
|
||||||
|
});
|
||||||
|
|
||||||
|
HyprlandAPI::addDispatcherV2(PHANDLE, "workspacehistory:cycle", ::dispatchCycle);
|
||||||
|
HyprlandAPI::addDispatcherV2(PHANDLE, "workspacehistory:commit", ::dispatchCommit);
|
||||||
|
HyprlandAPI::addDispatcherV2(PHANDLE, "workspacehistory:cancel", ::dispatchCancel);
|
||||||
|
HyprlandAPI::addDispatcherV2(PHANDLE, "workspacehistory:debug", ::dispatchDebug);
|
||||||
|
HyprlandAPI::addLuaFunction(PHANDLE, "workspacehistory", "cycle", ::luaCycle);
|
||||||
|
HyprlandAPI::addLuaFunction(PHANDLE, "workspacehistory", "commit", ::luaCommit);
|
||||||
|
HyprlandAPI::addLuaFunction(PHANDLE, "workspacehistory", "cancel", ::luaCancel);
|
||||||
|
HyprlandAPI::addLuaFunction(PHANDLE, "workspacehistory", "debug", ::luaDebug);
|
||||||
|
|
||||||
|
g_workspaceHistory.seedActiveWorkspaces();
|
||||||
|
HyprlandAPI::addNotification(PHANDLE, "[workspace-history] Initialized", CHyprColor{0.2, 1.0, 0.2, 1.0}, 3000);
|
||||||
|
return {"hypr-workspace-history", "Workspace history cycling with modifier-release commits", "Ivan Malison", "0.1.0"};
|
||||||
|
}
|
||||||
|
|
||||||
|
APICALL EXPORT void PLUGIN_EXIT() {
|
||||||
|
g_workspaceHistory.commit();
|
||||||
|
}
|
||||||
10
dotfiles/config/taffybar/flake.lock
generated
10
dotfiles/config/taffybar/flake.lock
generated
@@ -136,11 +136,11 @@
|
|||||||
"xmonad-contrib": "xmonad-contrib"
|
"xmonad-contrib": "xmonad-contrib"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1777319252,
|
"lastModified": 1777525523,
|
||||||
"narHash": "sha256-mPft6i8ReJAvW2LdylFI6FF6NFGa1HMa3RNbisfAsbc=",
|
"narHash": "sha256-/LGaCcX6BgXRYpWnRp9CNgAgy7lmbQsubi4RwHJgnTI=",
|
||||||
"ref": "refs/heads/codex/fix-gdk-backend-strut-detection",
|
"ref": "refs/heads/master",
|
||||||
"rev": "c2cee23fc57384cd322d589944129e6c31d4f0fd",
|
"rev": "23dbc827adca706b28df7404624ae3f5e800b04f",
|
||||||
"revCount": 2288,
|
"revCount": 2296,
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "file:///home/imalison/dotfiles/dotfiles/config/taffybar/taffybar"
|
"url": "file:///home/imalison/dotfiles/dotfiles/config/taffybar/taffybar"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -121,13 +121,6 @@
|
|||||||
(oa: {
|
(oa: {
|
||||||
doHaddock = false;
|
doHaddock = false;
|
||||||
doCheck = false;
|
doCheck = false;
|
||||||
# Legacy fix for older GHC (harmless on newer)
|
|
||||||
postPatch = (oa.postPatch or "") + ''
|
|
||||||
substituteInPlace src/System/Taffybar/DBus/Client/Util.hs \
|
|
||||||
--replace-fail "import Control.Monad (forM)" \
|
|
||||||
"import Control.Monad (forM)
|
|
||||||
import Control.Applicative (liftA2)"
|
|
||||||
'';
|
|
||||||
# Needed for gi-gtk-layer-shell (introspection data).
|
# Needed for gi-gtk-layer-shell (introspection data).
|
||||||
librarySystemDepends = (oa.librarySystemDepends or []) ++ [ pkgs.gtk-layer-shell ];
|
librarySystemDepends = (oa.librarySystemDepends or []) ++ [ pkgs.gtk-layer-shell ];
|
||||||
});
|
});
|
||||||
|
|||||||
8
dotfiles/config/taffybar/icons/claude-symbol.svg
Normal file
8
dotfiles/config/taffybar/icons/claude-symbol.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 174 148.18">
|
||||||
|
<path
|
||||||
|
fill="#d97757"
|
||||||
|
d="m 105.01,322.07 29.14,-16.35 0.49,-1.42 -0.49,-0.79 h -1.42 l -4.87,-0.3 -16.65,-0.45 -14.44,-0.6 -13.99,-0.75 -3.52,-0.75 -3.3,-4.35 0.34,-2.17 2.96,-1.99 4.24,0.37 9.37,0.64 14.06,0.97 10.2,0.6 15.11,1.57 h 2.4 l 0.34,-0.97 -0.82,-0.6 -0.64,-0.6 -14.55,-9.86 -15.75,-10.42 -8.25,-6 -4.46,-3.04 -2.25,-2.85 -0.97,-6.22 4.05,-4.46 5.44,0.37 1.39,0.37 5.51,4.24 11.77,9.11 15.37,11.32 2.25,1.87 0.9,-0.64 0.11,-0.45 -1.01,-1.69 -8.36,-15.11 -8.92,-15.37 -3.97,-6.37 -1.05,-3.82 c -0.37,-1.57 -0.64,-2.89 -0.64,-4.5 l 4.61,-6.26 2.55,-0.82 6.15,0.82 2.59,2.25 3.82,8.74 6.19,13.76 9.6,18.71 2.81,5.55 1.5,5.14 0.56,1.57 h 0.97 v -0.9 l 0.79,-10.54 1.46,-12.94 1.42,-16.65 0.49,-4.69 2.32,-5.62 4.61,-3.04 3.6,1.72 2.96,4.24 -0.41,2.74 -1.76,11.44 -3.45,17.92 -2.25,12 h 1.31 l 1.5,-1.5 6.07,-8.06 10.2,-12.75 4.5,-5.06 5.25,-5.59 3.37,-2.66 h 6.37 l 4.69,6.97 -2.1,7.2 -6.56,8.32 -5.44,7.05 -7.8,10.5 -4.87,8.4 0.45,0.67 1.16,-0.11 17.62,-3.75 9.52,-1.72 11.36,-1.95 5.14,2.4 0.56,2.44 -2.02,4.99 -12.15,3 -14.25,2.85 -21.22,5.02 -0.26,0.19 0.3,0.37 9.56,0.9 4.09,0.22 h 10.01 l 18.64,1.39 4.87,3.22 2.92,3.94 -0.49,3 -7.5,3.82 -10.12,-2.4 -23.62,-5.62 -8.1,-2.02 h -1.12 v 0.67 l 6.75,6.6 12.37,11.17 15.49,14.4 0.79,3.56 -1.99,2.81 -2.1,-0.3 -13.61,-10.24 -5.25,-4.61 -11.89,-10.01 h -0.79 v 1.05 l 2.74,4.01 14.47,21.75 0.75,6.67 -1.05,2.17 -3.75,1.31 -4.12,-0.75 -8.47,-11.89 -8.74,-13.39 -7.05,-12 -0.86,0.49 -4.16,44.81 -1.95,2.29 -4.5,1.72 -3.75,-2.85 -1.99,-4.61 1.99,-9.11 2.4,-11.89 1.95,-9.45 1.76,-11.74 1.05,-3.9 -0.07,-0.26 -0.86,0.11 -8.85,12.15 -13.46,18.19 -10.65,11.4 -2.55,1.01 -4.42,-2.29 0.41,-4.09 2.47,-3.64 14.74,-18.75 8.89,-11.62 5.74,-6.71 -0.04,-0.97 h -0.34 l -39.15,25.42 -6.97,0.9 -3,-2.81 0.37,-4.61 1.42,-1.5 11.77,-8.1 -0.04,0.04 z"
|
||||||
|
transform="translate(-75.96,-223.53)"
|
||||||
|
shape-rendering="optimizeQuality" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.9 KiB |
5
dotfiles/config/taffybar/icons/openai-symbol.svg
Normal file
5
dotfiles/config/taffybar/icons/openai-symbol.svg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 158.7128 157.296">
|
||||||
|
<!-- Generator: Adobe Illustrator 29.2.1, SVG Export Plug-In . SVG Version: 2.1.0 Build 116) -->
|
||||||
|
<path fill="#e7e4ee" d="M60.8734,57.2556v-14.9432c0-1.2586.4722-2.2029,1.5728-2.8314l30.0443-17.3023c4.0899-2.3593,8.9662-3.4599,13.9988-3.4599,18.8759,0,30.8307,14.6289,30.8307,30.2006,0,1.1007,0,2.3593-.158,3.6178l-31.1446-18.2467c-1.8872-1.1006-3.7754-1.1006-5.6629,0l-39.4812,22.9651ZM131.0276,115.4561v-35.7074c0-2.2028-.9446-3.7756-2.8318-4.8763l-39.481-22.9651,12.8982-7.3934c1.1007-.6285,2.0453-.6285,3.1458,0l30.0441,17.3024c8.6523,5.0341,14.4708,15.7296,14.4708,26.1107,0,11.9539-7.0769,22.965-18.2461,27.527v.0021ZM51.593,83.9964l-12.8982-7.5497c-1.1007-.6285-1.5728-1.5728-1.5728-2.8314v-34.6048c0-16.8303,12.8982-29.5722,30.3585-29.5722,6.607,0,12.7403,2.2029,17.9324,6.1349l-30.987,17.9324c-1.8871,1.1007-2.8314,2.6735-2.8314,4.8764v45.6159l-.0014-.0015ZM79.3562,100.0403l-18.4829-10.3811v-22.0209l18.4829-10.3811,18.4812,10.3811v22.0209l-18.4812,10.3811ZM91.2319,147.8591c-6.607,0-12.7403-2.2031-17.9324-6.1344l30.9866-17.9333c1.8872-1.1005,2.8318-2.6728,2.8318-4.8759v-45.616l13.0564,7.5498c1.1005.6285,1.5723,1.5728,1.5723,2.8314v34.6051c0,16.8297-13.0564,29.5723-30.5147,29.5723v.001ZM53.9522,112.7822l-30.0443-17.3024c-8.652-5.0343-14.471-15.7296-14.471-26.1107,0-12.1119,7.2356-22.9652,18.403-27.5272v35.8634c0,2.2028.9443,3.7756,2.8314,4.8763l39.3248,22.8068-12.8982,7.3938c-1.1007.6287-2.045.6287-3.1456,0ZM52.2229,138.5791c-17.7745,0-30.8306-13.3713-30.8306-29.8871,0-1.2585.1578-2.5169.3143-3.7754l30.987,17.9323c1.8871,1.1005,3.7757,1.1005,5.6628,0l39.4811-22.807v14.9435c0,1.2585-.4721,2.2021-1.5728,2.8308l-30.0443,17.3025c-4.0898,2.359-8.9662,3.4605-13.9989,3.4605h.0014ZM91.2319,157.296c19.0327,0,34.9188-13.5272,38.5383-31.4594,17.6164-4.562,28.9425-21.0779,28.9425-37.908,0-11.0112-4.719-21.7066-13.2133-29.4143.7867-3.3035,1.2595-6.607,1.2595-9.909,0-22.4929-18.2471-39.3247-39.3251-39.3247-4.2461,0-8.3363.6285-12.4262,2.045-7.0792-6.9213-16.8318-11.3254-27.5271-11.3254-19.0331,0-34.9191,13.5268-38.5384,31.4591C11.3255,36.0212,0,52.5373,0,69.3675c0,11.0112,4.7184,21.7065,13.2125,29.4142-.7865,3.3035-1.2586,6.6067-1.2586,9.9092,0,22.4923,18.2466,39.3241,39.3248,39.3241,4.2462,0,8.3362-.6277,12.426-2.0441,7.0776,6.921,16.8302,11.3251,27.5271,11.3251Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
Submodule dotfiles/config/taffybar/taffybar updated: 59e3c75990...23dbc827ad
@@ -40,6 +40,16 @@
|
|||||||
-GtkLabel-justify: left;
|
-GtkLabel-justify: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Compact logo column for stacked AI usage sections. */
|
||||||
|
.usage-section.icon-label > .icon {
|
||||||
|
min-width: 22px;
|
||||||
|
padding-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usage-section.icon-label > .label {
|
||||||
|
padding-right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Compact two-line RAM/SWAP widget: reduce icon padding a bit. */
|
/* Compact two-line RAM/SWAP widget: reduce icon padding a bit. */
|
||||||
.ram-swap .icon-label > .icon {
|
.ram-swap .icon-label > .icon {
|
||||||
/* Different glyphs have different visual widths; fix the icon column width
|
/* Different glyphs have different visual widths; fix the icon column width
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{-# LANGUAGE DataKinds #-}
|
{-# LANGUAGE DataKinds #-}
|
||||||
|
{-# LANGUAGE LambdaCase #-}
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
module Main (main) where
|
module Main (main) where
|
||||||
@@ -46,11 +47,21 @@ import System.Taffybar.SimpleConfig
|
|||||||
import System.Taffybar.Util (getPixbufFromFilePath, maybeTCombine, postGUIASync, (<|||>))
|
import System.Taffybar.Util (getPixbufFromFilePath, maybeTCombine, postGUIASync, (<|||>))
|
||||||
import System.Taffybar.Widget
|
import System.Taffybar.Widget
|
||||||
import qualified System.Taffybar.Widget.ASUS as ASUS
|
import qualified System.Taffybar.Widget.ASUS as ASUS
|
||||||
import System.Taffybar.Widget.AnthropicUsage (anthropicUsageStackNew)
|
import System.Taffybar.Widget.AnthropicUsage
|
||||||
|
( AnthropicUsageDisplayMode (AnthropicUsageDisplayRemaining),
|
||||||
|
AnthropicUsageStackConfig (..),
|
||||||
|
anthropicUsageStackNewWith,
|
||||||
|
defaultAnthropicUsageStackConfig,
|
||||||
|
)
|
||||||
import System.Taffybar.Widget.CPUMonitor (cpuMonitorNew)
|
import System.Taffybar.Widget.CPUMonitor (cpuMonitorNew)
|
||||||
import System.Taffybar.Widget.Generic.Graph (GraphConfig (..), GraphDirection (..), GraphStyle (..), defaultGraphConfig)
|
import System.Taffybar.Widget.Generic.Graph (GraphConfig (..), GraphDirection (..), GraphStyle (..), defaultGraphConfig)
|
||||||
import qualified System.Taffybar.Widget.NetworkManager as NetworkManager
|
import qualified System.Taffybar.Widget.NetworkManager as NetworkManager
|
||||||
import System.Taffybar.Widget.OpenAIUsage (openAIUsageStackNew)
|
import System.Taffybar.Widget.OpenAIUsage
|
||||||
|
( OpenAIUsageDisplayMode (OpenAIUsageDisplayRemaining),
|
||||||
|
OpenAIUsageStackConfig (..),
|
||||||
|
defaultOpenAIUsageStackConfig,
|
||||||
|
openAIUsageStackNewWith,
|
||||||
|
)
|
||||||
import qualified System.Taffybar.Widget.PulseAudio as PulseAudio
|
import qualified System.Taffybar.Widget.PulseAudio as PulseAudio
|
||||||
import System.Taffybar.Widget.SNIMenu (withNmAppletMenu)
|
import System.Taffybar.Widget.SNIMenu (withNmAppletMenu)
|
||||||
import System.Taffybar.Widget.SNITray
|
import System.Taffybar.Widget.SNITray
|
||||||
@@ -65,7 +76,7 @@ import System.Taffybar.Widget.SNITray.PrioritizedCollapsible
|
|||||||
sniTrayPrioritizedCollapsibleNewFromParams,
|
sniTrayPrioritizedCollapsibleNewFromParams,
|
||||||
)
|
)
|
||||||
import qualified System.Taffybar.Widget.ScreenLock as ScreenLock
|
import qualified System.Taffybar.Widget.ScreenLock as ScreenLock
|
||||||
import System.Taffybar.Widget.Util (backgroundLoop, buildContentsBox, buildIconLabelBox, loadPixbufByName, widgetSetClassGI)
|
import System.Taffybar.Widget.Util (backgroundLoop, buildContentsBox, buildIconLabelBox, loadPixbufByName, pixbufNewFromFileAtScaleByHeight, widgetSetClassGI)
|
||||||
import qualified System.Taffybar.Widget.Wlsunset as Wlsunset
|
import qualified System.Taffybar.Widget.Wlsunset as Wlsunset
|
||||||
import qualified System.Taffybar.Widget.Workspaces as Workspaces
|
import qualified System.Taffybar.Widget.Workspaces as Workspaces
|
||||||
import System.Taffybar.WindowIcon (pixBufFromColor)
|
import System.Taffybar.WindowIcon (pixBufFromColor)
|
||||||
@@ -507,10 +518,10 @@ simplifiedScreensaverWidget =
|
|||||||
then return False
|
then return False
|
||||||
else case button of
|
else case button of
|
||||||
1 -> do
|
1 -> do
|
||||||
void $ spawnCommand "hypr-screensaver toggle >/dev/null 2>&1"
|
void $ spawnCommand "/home/imalison/dotfiles/dotfiles/lib/bin/hypr-screensaver toggle >/dev/null 2>&1"
|
||||||
return True
|
return True
|
||||||
3 -> do
|
3 -> do
|
||||||
void $ spawnCommand "hypr-screensaver stop >/dev/null 2>&1"
|
void $ spawnCommand "/home/imalison/dotfiles/dotfiles/lib/bin/hypr-screensaver stop >/dev/null 2>&1"
|
||||||
return True
|
return True
|
||||||
_ -> return False
|
_ -> return False
|
||||||
Gtk.widgetShowAll ebox
|
Gtk.widgetShowAll ebox
|
||||||
@@ -551,13 +562,40 @@ wakeupDebugWidget :: TaffyIO Gtk.Widget
|
|||||||
wakeupDebugWidget =
|
wakeupDebugWidget =
|
||||||
decorateWithClassAndBoxM "wakeup-debug" wakeupDebugWidgetNew
|
decorateWithClassAndBoxM "wakeup-debug" wakeupDebugWidgetNew
|
||||||
|
|
||||||
|
usageLogoWidget :: FilePath -> Text -> IO Gtk.Widget
|
||||||
|
usageLogoWidget iconFile tooltip = do
|
||||||
|
iconPath <- getUserConfigFile "taffybar" ("icons/" <> iconFile)
|
||||||
|
iconWidget <-
|
||||||
|
pixbufNewFromFileAtScaleByHeight 18 iconPath >>= \case
|
||||||
|
Right pixbuf -> Gtk.toWidget =<< Gtk.imageNewFromPixbuf (Just pixbuf)
|
||||||
|
Left _ -> Gtk.toWidget =<< Gtk.labelNew (Just "?")
|
||||||
|
Gtk.widgetSetTooltipText iconWidget (Just tooltip)
|
||||||
|
widgetSetClassGI iconWidget "usage-logo"
|
||||||
|
|
||||||
|
usageSectionWidget :: Text -> FilePath -> Text -> TaffyIO Gtk.Widget -> TaffyIO Gtk.Widget
|
||||||
|
usageSectionWidget klass iconFile tooltip stackBuilder =
|
||||||
|
decorateWithClassAndBoxM klass $ do
|
||||||
|
stack <- stackBuilder
|
||||||
|
liftIO $ do
|
||||||
|
iconWidget <- usageLogoWidget iconFile tooltip
|
||||||
|
section <- buildIconLabelBox iconWidget stack
|
||||||
|
widgetSetClassGI section "usage-section"
|
||||||
|
|
||||||
openAIUsageWidget :: TaffyIO Gtk.Widget
|
openAIUsageWidget :: TaffyIO Gtk.Widget
|
||||||
openAIUsageWidget =
|
openAIUsageWidget =
|
||||||
decorateWithClassAndBoxM "openai-usage" openAIUsageStackNew
|
usageSectionWidget "openai-usage" "openai-symbol.svg" "OpenAI usage" $
|
||||||
|
openAIUsageStackNewWith
|
||||||
|
defaultOpenAIUsageStackConfig
|
||||||
|
{ openAIUsageStackDefaultDisplayMode = OpenAIUsageDisplayRemaining
|
||||||
|
}
|
||||||
|
|
||||||
anthropicUsageWidget :: TaffyIO Gtk.Widget
|
anthropicUsageWidget :: TaffyIO Gtk.Widget
|
||||||
anthropicUsageWidget =
|
anthropicUsageWidget =
|
||||||
decorateWithClassAndBoxM "anthropic-usage" anthropicUsageStackNew
|
usageSectionWidget "anthropic-usage" "claude-symbol.svg" "Anthropic usage" $
|
||||||
|
anthropicUsageStackNewWith
|
||||||
|
defaultAnthropicUsageStackConfig
|
||||||
|
{ anthropicUsageStackDefaultDisplayMode = AnthropicUsageDisplayRemaining
|
||||||
|
}
|
||||||
|
|
||||||
sniPriorityVisibilityThresholdDefault :: Int
|
sniPriorityVisibilityThresholdDefault :: Int
|
||||||
sniPriorityVisibilityThresholdDefault = 0
|
sniPriorityVisibilityThresholdDefault = 0
|
||||||
|
|||||||
@@ -232,27 +232,19 @@ getWorkspaceDmenu = myDmenu (workspaces myConfig)
|
|||||||
|
|
||||||
-- Selectors
|
-- Selectors
|
||||||
|
|
||||||
isGmailTitle t = isInfixOf "@gmail.com" t && isInfixOf "Gmail" t
|
|
||||||
isMessagesTitle = isPrefixOf "Messages"
|
|
||||||
isChromeClass = isInfixOf "chrome"
|
isChromeClass = isInfixOf "chrome"
|
||||||
noSpecialChromeTitles = helper <$> title
|
|
||||||
where helper t = not $ any ($ t) [isGmailTitle, isMessagesTitle]
|
|
||||||
chromeSelectorBase = isChromeClass <$> className
|
chromeSelectorBase = isChromeClass <$> className
|
||||||
|
|
||||||
chromeSelector = chromeSelectorBase <&&> noSpecialChromeTitles
|
chromeSelector = chromeSelectorBase
|
||||||
elementSelector = className =? "Element"
|
elementSelector = className =? "Element"
|
||||||
emacsSelector = className =? "Emacs"
|
emacsSelector = className =? "Emacs"
|
||||||
gmailSelector = chromeSelectorBase <&&> fmap isGmailTitle title
|
|
||||||
messagesSelector = chromeSelectorBase <&&> isMessagesTitle <$> title
|
|
||||||
slackSelector = className =? "Slack"
|
slackSelector = className =? "Slack"
|
||||||
spotifySelector = className =? "Spotify"
|
spotifySelector = className =? "Spotify"
|
||||||
transmissionSelector = fmap (isPrefixOf "Transmission") title
|
transmissionSelector = fmap (isPrefixOf "Transmission") title
|
||||||
volumeSelector = className =? "Pavucontrol"
|
volumeSelector = className =? "Pavucontrol"
|
||||||
|
|
||||||
virtualClasses =
|
virtualClasses =
|
||||||
[ (gmailSelector, "Gmail")
|
[ (chromeSelector, "Chrome")
|
||||||
, (messagesSelector, "Messages")
|
|
||||||
, (chromeSelector, "Chrome")
|
|
||||||
, (transmissionSelector, "Transmission")
|
, (transmissionSelector, "Transmission")
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -261,11 +253,7 @@ virtualClasses =
|
|||||||
chromeCommand = "google-chrome-stable"
|
chromeCommand = "google-chrome-stable"
|
||||||
elementCommand = "element-desktop"
|
elementCommand = "element-desktop"
|
||||||
emacsCommand = "emacsclient -c"
|
emacsCommand = "emacsclient -c"
|
||||||
gmailCommand =
|
|
||||||
"google-chrome-stable --new-window https://mail.google.com/mail/u/0/#inbox"
|
|
||||||
htopCommand = "ghostty --title=htop -e htop"
|
htopCommand = "ghostty --title=htop -e htop"
|
||||||
messagesCommand =
|
|
||||||
"google-chrome-stable --new-window https://messages.google.com/web/conversations"
|
|
||||||
slackCommand = "slack"
|
slackCommand = "slack"
|
||||||
spotifyCommand = "spotify"
|
spotifyCommand = "spotify"
|
||||||
transmissionCommand = "transmission-gtk"
|
transmissionCommand = "transmission-gtk"
|
||||||
@@ -813,9 +801,7 @@ nearFullFloat = customFloating $ W.RationalRect l t w h
|
|||||||
|
|
||||||
scratchpads =
|
scratchpads =
|
||||||
[ NS "element" elementCommand elementSelector nearFullFloat
|
[ NS "element" elementCommand elementSelector nearFullFloat
|
||||||
, NS "gmail" gmailCommand gmailSelector nearFullFloat
|
|
||||||
, NS "htop" htopCommand (title =? "htop") nearFullFloat
|
, NS "htop" htopCommand (title =? "htop") nearFullFloat
|
||||||
, NS "messages" messagesCommand messagesSelector nearFullFloat
|
|
||||||
, NS "slack" slackCommand slackSelector nearFullFloat
|
, NS "slack" slackCommand slackSelector nearFullFloat
|
||||||
, NS "spotify" spotifyCommand spotifySelector nearFullFloat
|
, NS "spotify" spotifyCommand spotifySelector nearFullFloat
|
||||||
, NS "transmission" transmissionCommand transmissionSelector nearFullFloat
|
, NS "transmission" transmissionCommand transmissionSelector nearFullFloat
|
||||||
@@ -1025,9 +1011,7 @@ addKeys conf@XConfig { modMask = modm } =
|
|||||||
|
|
||||||
-- ScratchPads
|
-- ScratchPads
|
||||||
[ ((modalt, xK_e), doScratchpad "element")
|
[ ((modalt, xK_e), doScratchpad "element")
|
||||||
, ((modalt, xK_g), doScratchpad "gmail")
|
|
||||||
, ((modalt, xK_h), doScratchpad "htop")
|
, ((modalt, xK_h), doScratchpad "htop")
|
||||||
, ((modalt, xK_m), doScratchpad "messages")
|
|
||||||
, ((modalt, xK_k), doScratchpad "slack")
|
, ((modalt, xK_k), doScratchpad "slack")
|
||||||
, ((modalt, xK_s), doScratchpad "spotify")
|
, ((modalt, xK_s), doScratchpad "spotify")
|
||||||
, ((modalt, xK_t), doScratchpad "transmission")
|
, ((modalt, xK_t), doScratchpad "transmission")
|
||||||
|
|||||||
93
dotfiles/lib/bin/desktop_shell_ui
Executable file
93
dotfiles/lib/bin/desktop_shell_ui
Executable file
@@ -0,0 +1,93 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
state_file="${IM_DESKTOP_SHELL_UI_STATE:-${XDG_STATE_HOME:-$HOME/.local/state}/imalison/desktop-shell-ui}"
|
||||||
|
default_shell_ui="${IM_HYPRLAND_SHELL_UI:-taffybar}"
|
||||||
|
|
||||||
|
normalize_shell_ui() {
|
||||||
|
case "${1:-}" in
|
||||||
|
noctalia)
|
||||||
|
printf '%s\n' "noctalia"
|
||||||
|
;;
|
||||||
|
taffybar|rofi)
|
||||||
|
printf '%s\n' "taffybar"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
current_shell_ui() {
|
||||||
|
local configured=""
|
||||||
|
|
||||||
|
if [[ -r "$state_file" ]]; then
|
||||||
|
IFS= read -r configured < "$state_file" || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
normalize_shell_ui "$configured" 2>/dev/null \
|
||||||
|
|| normalize_shell_ui "$default_shell_ui" 2>/dev/null \
|
||||||
|
|| printf '%s\n' "taffybar"
|
||||||
|
}
|
||||||
|
|
||||||
|
write_shell_ui() {
|
||||||
|
local shell_ui="$1"
|
||||||
|
mkdir -p "$(dirname "$state_file")"
|
||||||
|
printf '%s\n' "$shell_ui" > "$state_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
apply_shell_ui() {
|
||||||
|
local shell_ui="$1"
|
||||||
|
|
||||||
|
export IM_HYPRLAND_SHELL_UI="$shell_ui"
|
||||||
|
systemctl --user import-environment IM_HYPRLAND_SHELL_UI 2>/dev/null || true
|
||||||
|
|
||||||
|
case "$shell_ui" in
|
||||||
|
noctalia)
|
||||||
|
systemctl --user stop taffybar.service 2>/dev/null || true
|
||||||
|
systemctl --user reset-failed taffybar.service 2>/dev/null || true
|
||||||
|
systemctl --user start noctalia-shell.service
|
||||||
|
;;
|
||||||
|
taffybar)
|
||||||
|
systemctl --user stop noctalia-shell.service 2>/dev/null || true
|
||||||
|
systemctl --user reset-failed noctalia-shell.service 2>/dev/null || true
|
||||||
|
systemctl --user start taffybar.service
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
set_shell_ui() {
|
||||||
|
local shell_ui
|
||||||
|
shell_ui="$(normalize_shell_ui "${1:-}")" || {
|
||||||
|
echo "usage: desktop_shell_ui set {taffybar|noctalia}" >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
write_shell_ui "$shell_ui"
|
||||||
|
apply_shell_ui "$shell_ui"
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-current}" in
|
||||||
|
current)
|
||||||
|
current_shell_ui
|
||||||
|
;;
|
||||||
|
set)
|
||||||
|
set_shell_ui "${2:-}"
|
||||||
|
;;
|
||||||
|
toggle)
|
||||||
|
case "$(current_shell_ui)" in
|
||||||
|
noctalia) set_shell_ui taffybar ;;
|
||||||
|
*) set_shell_ui noctalia ;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
apply)
|
||||||
|
apply_shell_ui "$(current_shell_ui)"
|
||||||
|
;;
|
||||||
|
exec-condition)
|
||||||
|
[[ "$(current_shell_ui)" == "$(normalize_shell_ui "${2:-}")" ]]
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "usage: desktop_shell_ui {current|set|toggle|apply|exec-condition}" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
@@ -2,27 +2,48 @@
|
|||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
script_path="$(readlink -f "${BASH_SOURCE[0]}")"
|
|
||||||
state_dir="${XDG_RUNTIME_DIR:-/tmp}/hypr-screensaver"
|
state_dir="${XDG_RUNTIME_DIR:-/tmp}/hypr-screensaver"
|
||||||
|
pid_file="$state_dir/mpvpaper.pid"
|
||||||
|
event_log="$state_dir/events.log"
|
||||||
mkdir -p "$state_dir"
|
mkdir -p "$state_dir"
|
||||||
|
|
||||||
title_prefix="hypr-screensaver:"
|
title_prefix="hypr-screensaver:"
|
||||||
|
screensaver_dir="${HYPR_SCREENSAVER_DIR:-/var/lib/syncthing/sync/Screensaver}"
|
||||||
|
screensaver_use_dir="${HYPR_SCREENSAVER_USE_DIR:-$screensaver_dir/use}"
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
Usage: hypr-screensaver <start|stop|toggle|status|session>
|
Usage: hypr-screensaver <start|stop|toggle|status|session>
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
start Launch the screensaver on every Hyprland monitor.
|
start Launch the screensaver as a Wayland layer-shell overlay.
|
||||||
stop Stop any running screensaver windows.
|
stop Stop any running screensaver overlay.
|
||||||
toggle Start if stopped, otherwise stop.
|
toggle Start if stopped, otherwise stop.
|
||||||
status Exit 0 if any screensaver window is running, otherwise exit 1.
|
status Exit 0 if the screensaver overlay is running, otherwise exit 1.
|
||||||
session Run the configured screensaver payload for one monitor.
|
session Compatibility alias for start.
|
||||||
|
|
||||||
The default payload is an mpv-rendered lavfi animation. You can override the
|
By default, start chooses a random media file from:
|
||||||
source with HYPR_SCREENSAVER_SOURCE, for example:
|
/var/lib/syncthing/sync/Screensaver/use
|
||||||
|
|
||||||
|
Populate that directory with symlinks to generated screensaver loops you want
|
||||||
|
in rotation. You can override the source with HYPR_SCREENSAVER_SOURCE, for
|
||||||
|
example:
|
||||||
HYPR_SCREENSAVER_SOURCE='/path/to/video.mp4'
|
HYPR_SCREENSAVER_SOURCE='/path/to/video.mp4'
|
||||||
HYPR_SCREENSAVER_SOURCE='av://lavfi:mandelbrot=s=2560x1440:r=60'
|
HYPR_SCREENSAVER_SOURCE='av://lavfi:mandelbrot=s=2560x1440:r=60'
|
||||||
|
|
||||||
|
You can also override the rotation directory:
|
||||||
|
HYPR_SCREENSAVER_USE_DIR='/path/to/use'
|
||||||
|
|
||||||
|
Layer-shell/output overrides:
|
||||||
|
HYPR_SCREENSAVER_OUTPUT='ALL'
|
||||||
|
HYPR_SCREENSAVER_LAYER='overlay'
|
||||||
|
|
||||||
|
HDR handling defaults to matching Hyprland's monitor color-management preset.
|
||||||
|
Only monitors with preset "hdr" or "hdredid" get HDR colorspace hints. Override
|
||||||
|
with:
|
||||||
|
HYPR_SCREENSAVER_HDR_MODE=auto
|
||||||
|
HYPR_SCREENSAVER_HDR_MODE=sdr
|
||||||
|
HYPR_SCREENSAVER_HDR_MODE=hdr
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,19 +51,11 @@ monitors_json() {
|
|||||||
hyprctl -j monitors
|
hyprctl -j monitors
|
||||||
}
|
}
|
||||||
|
|
||||||
monitor_names() {
|
log_event() {
|
||||||
monitors_json | jq -r '.[].name'
|
printf '%s %s\n' "$(date --iso-8601=seconds)" "$*" >>"$event_log"
|
||||||
}
|
}
|
||||||
|
|
||||||
monitor_specs() {
|
legacy_screensaver_window_pids() {
|
||||||
monitors_json | jq -c '.[] | { name, width, height }'
|
|
||||||
}
|
|
||||||
|
|
||||||
focused_monitor() {
|
|
||||||
monitors_json | jq -r '.[] | select(.focused) | .name'
|
|
||||||
}
|
|
||||||
|
|
||||||
screensaver_window_pids() {
|
|
||||||
hyprctl -j clients 2>/dev/null | jq -r --arg prefix "$title_prefix" '
|
hyprctl -j clients 2>/dev/null | jq -r --arg prefix "$title_prefix" '
|
||||||
.[]
|
.[]
|
||||||
| select((.title // "") | startswith($prefix))
|
| select((.title // "") | startswith($prefix))
|
||||||
@@ -52,106 +65,170 @@ screensaver_window_pids() {
|
|||||||
|
|
||||||
is_running() {
|
is_running() {
|
||||||
local pid
|
local pid
|
||||||
for pid in $(screensaver_window_pids); do
|
if [ -f "$pid_file" ]; then
|
||||||
if kill -0 "$pid" 2>/dev/null; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
shopt -s nullglob
|
|
||||||
local pid_file
|
|
||||||
for pid_file in "$state_dir"/*.pid; do
|
|
||||||
pid="$(<"$pid_file")"
|
pid="$(<"$pid_file")"
|
||||||
if kill -0 "$pid" 2>/dev/null; then
|
if kill -0 "$pid" 2>/dev/null; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
done
|
rm -f "$pid_file"
|
||||||
|
fi
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
default_source() {
|
default_source() {
|
||||||
local width="$1"
|
local size width height
|
||||||
local height="$2"
|
size="$(
|
||||||
|
monitors_json 2>/dev/null \
|
||||||
|
| jq -r 'max_by((.width // 0) * (.height // 0)) | "\(.width // 1920)x\(.height // 1080)"' 2>/dev/null \
|
||||||
|
|| true
|
||||||
|
)"
|
||||||
|
size="${size:-1920x1080}"
|
||||||
|
width="${size%x*}"
|
||||||
|
height="${size#*x}"
|
||||||
|
|
||||||
printf 'av://lavfi:life=s=%sx%s:r=60:mold=10:ratio=0.065:death_color=#101414:life_color=#7dd3fc:mold_color=#1e3a5f,format=yuv420p' \
|
printf 'av://lavfi:life=s=%sx%s:r=60:mold=10:ratio=0.065:death_color=#101414:life_color=#7dd3fc:mold_color=#1e3a5f,format=yuv420p' \
|
||||||
"$width" "$height"
|
"$width" "$height"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
random_source() {
|
||||||
|
[ -d "$screensaver_use_dir" ] || return 1
|
||||||
|
|
||||||
|
local -a candidates=()
|
||||||
|
local candidate
|
||||||
|
while IFS= read -r -d '' candidate; do
|
||||||
|
candidates+=("$candidate")
|
||||||
|
done < <(
|
||||||
|
find -L "$screensaver_use_dir" -maxdepth 1 -type f \
|
||||||
|
\( \
|
||||||
|
-iname '*.mp4' -o \
|
||||||
|
-iname '*.mkv' -o \
|
||||||
|
-iname '*.mov' -o \
|
||||||
|
-iname '*.webm' -o \
|
||||||
|
-iname '*.gif' -o \
|
||||||
|
-iname '*.png' -o \
|
||||||
|
-iname '*.jpg' -o \
|
||||||
|
-iname '*.jpeg' \
|
||||||
|
\) \
|
||||||
|
-print0
|
||||||
|
)
|
||||||
|
|
||||||
|
[ "${#candidates[@]}" -gt 0 ] || return 1
|
||||||
|
printf '%s\n' "${candidates[$((RANDOM % ${#candidates[@]}))]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
screensaver_uses_hdr() {
|
||||||
|
local mode="${HYPR_SCREENSAVER_HDR_MODE:-auto}"
|
||||||
|
|
||||||
|
case "$mode" in
|
||||||
|
hdr)
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
sdr)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
auto)
|
||||||
|
monitors_json 2>/dev/null \
|
||||||
|
| jq -e 'any(.[]; (.colorManagementPreset // "srgb") == "hdr" or (.colorManagementPreset // "srgb") == "hdredid")' >/dev/null 2>&1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf 'Invalid HYPR_SCREENSAVER_HDR_MODE=%s; expected auto, sdr, or hdr\n' "$mode" >&2
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
mpv_color_options() {
|
||||||
|
if screensaver_uses_hdr; then
|
||||||
|
printf '%s ' \
|
||||||
|
target-colorspace-hint=yes \
|
||||||
|
target-colorspace-hint-mode=source
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s ' \
|
||||||
|
target-colorspace-hint=no \
|
||||||
|
target-prim=bt.709 \
|
||||||
|
target-trc=srgb \
|
||||||
|
target-gamut=bt.709 \
|
||||||
|
target-peak=80 \
|
||||||
|
inverse-tone-mapping=no
|
||||||
|
}
|
||||||
|
|
||||||
|
mpv_options() {
|
||||||
|
if [ -n "${HYPR_SCREENSAVER_MPV_OPTIONS:-}" ]; then
|
||||||
|
printf '%s\n' "$HYPR_SCREENSAVER_MPV_OPTIONS"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s %s\n' \
|
||||||
|
"no-audio loop-file=inf osc=no osd-level=0 input-default-bindings=no terminal=no image-display-duration=inf keep-open=yes" \
|
||||||
|
"$(mpv_color_options)"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_mpvpaper() {
|
||||||
|
if command -v mpvpaper >/dev/null 2>&1; then
|
||||||
|
exec mpvpaper "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec nix shell nixpkgs#mpvpaper --command mpvpaper "$@"
|
||||||
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
local current_monitor spec monitor width height pid
|
local source output layer options pid
|
||||||
|
|
||||||
if is_running; then
|
if is_running; then
|
||||||
|
log_event "start ignored: already running pid=$(<"$pid_file")"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
current_monitor="$(focused_monitor || true)"
|
stop
|
||||||
|
|
||||||
while IFS= read -r spec; do
|
source="${HYPR_SCREENSAVER_SOURCE:-}"
|
||||||
monitor="$(jq -r '.name' <<<"$spec")"
|
if [ -z "$source" ]; then
|
||||||
width="$(jq -r '.width' <<<"$spec")"
|
source="$(random_source || true)"
|
||||||
height="$(jq -r '.height' <<<"$spec")"
|
|
||||||
[ -n "$monitor" ] || continue
|
|
||||||
HYPR_SCREENSAVER_MONITOR="$monitor" \
|
|
||||||
HYPR_SCREENSAVER_WIDTH="$width" \
|
|
||||||
HYPR_SCREENSAVER_HEIGHT="$height" \
|
|
||||||
"$script_path" session >/dev/null 2>&1 &
|
|
||||||
pid=$!
|
|
||||||
printf '%s\n' "$pid" > "$state_dir/${monitor}.pid"
|
|
||||||
sleep 0.15
|
|
||||||
done < <(monitor_specs)
|
|
||||||
|
|
||||||
if [ -n "$current_monitor" ]; then
|
|
||||||
hyprctl dispatch focusmonitor "$current_monitor" >/dev/null 2>&1 || true
|
|
||||||
fi
|
fi
|
||||||
|
if [ -z "$source" ]; then
|
||||||
|
source="$(default_source)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
output="${HYPR_SCREENSAVER_OUTPUT:-ALL}"
|
||||||
|
layer="${HYPR_SCREENSAVER_LAYER:-overlay}"
|
||||||
|
options="$(mpv_options)"
|
||||||
|
log_event "start output=$output layer=$layer source=$source"
|
||||||
|
|
||||||
|
(
|
||||||
|
exec </dev/null
|
||||||
|
run_mpvpaper --layer "$layer" --mpv-options "$options" "$output" "$source"
|
||||||
|
) >>"$state_dir/mpvpaper.log" 2>&1 &
|
||||||
|
pid=$!
|
||||||
|
printf '%s\n' "$pid" > "$pid_file"
|
||||||
|
sleep 0.2
|
||||||
|
if ! kill -0 "$pid" 2>/dev/null; then
|
||||||
|
rm -f "$pid_file"
|
||||||
|
log_event "start failed: process exited early pid=$pid"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
log_event "start ok pid=$pid"
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
local pid pid_file
|
local pid legacy_pid
|
||||||
|
|
||||||
for pid in $(screensaver_window_pids); do
|
if [ -f "$pid_file" ]; then
|
||||||
kill "$pid" >/dev/null 2>&1 || true
|
|
||||||
done
|
|
||||||
|
|
||||||
shopt -s nullglob
|
|
||||||
for pid_file in "$state_dir"/*.pid; do
|
|
||||||
pid="$(<"$pid_file")"
|
pid="$(<"$pid_file")"
|
||||||
|
log_event "stop pid=$pid"
|
||||||
kill "$pid" >/dev/null 2>&1 || true
|
kill "$pid" >/dev/null 2>&1 || true
|
||||||
|
pkill -TERM -P "$pid" >/dev/null 2>&1 || true
|
||||||
rm -f "$pid_file"
|
rm -f "$pid_file"
|
||||||
done
|
else
|
||||||
}
|
log_event "stop with no pid file"
|
||||||
|
|
||||||
session() {
|
|
||||||
local monitor="${HYPR_SCREENSAVER_MONITOR:?missing HYPR_SCREENSAVER_MONITOR}"
|
|
||||||
local width="${HYPR_SCREENSAVER_WIDTH:-1920}"
|
|
||||||
local height="${HYPR_SCREENSAVER_HEIGHT:-1080}"
|
|
||||||
local source="${HYPR_SCREENSAVER_SOURCE:-$(default_source "$width" "$height")}"
|
|
||||||
local -a mpv_args=(
|
|
||||||
--no-config
|
|
||||||
--really-quiet
|
|
||||||
--fullscreen
|
|
||||||
--fs-screen-name="$monitor"
|
|
||||||
--screen-name="$monitor"
|
|
||||||
--force-window=immediate
|
|
||||||
--border=no
|
|
||||||
--title-bar=no
|
|
||||||
--ontop
|
|
||||||
--keep-open=yes
|
|
||||||
--loop-file=inf
|
|
||||||
--audio=no
|
|
||||||
--osc=no
|
|
||||||
--osd-level=0
|
|
||||||
--input-default-bindings=no
|
|
||||||
--wayland-app-id=hypr-screensaver
|
|
||||||
--title="${title_prefix}${monitor}"
|
|
||||||
--image-display-duration=inf
|
|
||||||
"$source"
|
|
||||||
)
|
|
||||||
|
|
||||||
if command -v mpv >/dev/null 2>&1; then
|
|
||||||
exec mpv "${mpv_args[@]}"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exec nix shell nixpkgs#mpv --command mpv "${mpv_args[@]}"
|
for legacy_pid in $(legacy_screensaver_window_pids); do
|
||||||
|
log_event "stop legacy pid=$legacy_pid"
|
||||||
|
kill "$legacy_pid" >/dev/null 2>&1 || true
|
||||||
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
status() {
|
status() {
|
||||||
@@ -176,7 +253,7 @@ case "${1:-}" in
|
|||||||
status
|
status
|
||||||
;;
|
;;
|
||||||
session)
|
session)
|
||||||
session
|
start
|
||||||
;;
|
;;
|
||||||
""|-h|--help|help)
|
""|-h|--help|help)
|
||||||
usage
|
usage
|
||||||
|
|||||||
254
dotfiles/lib/bin/hypr_rofi_window
Executable file
254
dotfiles/lib/bin/hypr_rofi_window
Executable file
@@ -0,0 +1,254 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
PROMPTS = {
|
||||||
|
"go": "Go to window",
|
||||||
|
"bring": "Bring window",
|
||||||
|
"replace": "Replace with",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_hyprland_instance():
|
||||||
|
if os.environ.get("HYPRLAND_INSTANCE_SIGNATURE"):
|
||||||
|
return
|
||||||
|
|
||||||
|
runtime = Path(os.environ.get("XDG_RUNTIME_DIR", f"/run/user/{os.getuid()}"))
|
||||||
|
hypr_dir = runtime / "hypr"
|
||||||
|
if not hypr_dir.is_dir():
|
||||||
|
return
|
||||||
|
|
||||||
|
sockets = []
|
||||||
|
for socket in hypr_dir.glob("*/.socket.sock"):
|
||||||
|
try:
|
||||||
|
sockets.append((socket.stat().st_mtime, socket.parent.name))
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if sockets:
|
||||||
|
os.environ["HYPRLAND_INSTANCE_SIGNATURE"] = max(sockets)[1]
|
||||||
|
|
||||||
|
|
||||||
|
def run_json(*args):
|
||||||
|
result = subprocess.run(args, check=False, text=True, capture_output=True)
|
||||||
|
if result.returncode != 0 or not result.stdout.strip():
|
||||||
|
return None
|
||||||
|
return json.loads(result.stdout)
|
||||||
|
|
||||||
|
|
||||||
|
def dispatch_lua(expression):
|
||||||
|
return subprocess.run(["hyprctl", "dispatch", expression], check=False).returncode == 0
|
||||||
|
|
||||||
|
|
||||||
|
def dispatch_legacy(*args):
|
||||||
|
return subprocess.run(["hyprctl", "dispatch", *args], check=False).returncode == 0
|
||||||
|
|
||||||
|
|
||||||
|
def normal_workspace(window):
|
||||||
|
workspace = window.get("workspace") or {}
|
||||||
|
workspace_id = workspace.get("id")
|
||||||
|
return isinstance(workspace_id, int) and workspace_id >= 0
|
||||||
|
|
||||||
|
|
||||||
|
def window_address(window):
|
||||||
|
address = window.get("address") or ""
|
||||||
|
return address if isinstance(address, str) and address else None
|
||||||
|
|
||||||
|
|
||||||
|
def clean_text(value):
|
||||||
|
return str(value or "").replace("\t", " ").replace("\n", " ").strip()
|
||||||
|
|
||||||
|
|
||||||
|
def app_dirs():
|
||||||
|
seen = set()
|
||||||
|
for base in os.environ.get("XDG_DATA_DIRS", "/run/current-system/sw/share:/usr/share:/usr/local/share").split(":"):
|
||||||
|
path = Path(base) / "applications"
|
||||||
|
if path.is_dir() and path not in seen:
|
||||||
|
seen.add(path)
|
||||||
|
yield path
|
||||||
|
|
||||||
|
local = Path.home() / ".local/share/applications"
|
||||||
|
if local.is_dir() and local not in seen:
|
||||||
|
yield local
|
||||||
|
|
||||||
|
|
||||||
|
def parse_desktop_file(path):
|
||||||
|
icon = None
|
||||||
|
wm_class = None
|
||||||
|
try:
|
||||||
|
with path.open(errors="ignore") as handle:
|
||||||
|
for raw_line in handle:
|
||||||
|
line = raw_line.strip()
|
||||||
|
if not icon and line.startswith("Icon="):
|
||||||
|
icon = line.removeprefix("Icon=").strip()
|
||||||
|
elif not wm_class and line.startswith("StartupWMClass="):
|
||||||
|
wm_class = line.removeprefix("StartupWMClass=").strip()
|
||||||
|
if icon and wm_class:
|
||||||
|
break
|
||||||
|
except OSError:
|
||||||
|
return None, None
|
||||||
|
return icon, wm_class
|
||||||
|
|
||||||
|
|
||||||
|
def icon_map():
|
||||||
|
mapping = {}
|
||||||
|
for directory in app_dirs():
|
||||||
|
for desktop_file in directory.glob("*.desktop"):
|
||||||
|
icon, wm_class = parse_desktop_file(desktop_file)
|
||||||
|
if not icon:
|
||||||
|
continue
|
||||||
|
mapping.setdefault(desktop_file.stem.lower(), icon)
|
||||||
|
if wm_class:
|
||||||
|
mapping.setdefault(wm_class.lower(), icon)
|
||||||
|
return mapping
|
||||||
|
|
||||||
|
|
||||||
|
def icon_for(mapping, class_name):
|
||||||
|
class_key = clean_text(class_name).lower()
|
||||||
|
return mapping.get(class_key, class_key or "application-x-executable")
|
||||||
|
|
||||||
|
|
||||||
|
def rofi_index(entries, prompt):
|
||||||
|
menu = b"".join(entry.encode("utf-8", errors="replace") for entry in entries)
|
||||||
|
result = subprocess.run(
|
||||||
|
["rofi", "-dmenu", "-i", "-show-icons", "-p", prompt, "-format", "i"],
|
||||||
|
input=menu,
|
||||||
|
check=False,
|
||||||
|
capture_output=True,
|
||||||
|
)
|
||||||
|
if result.returncode != 0:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
return int(result.stdout.decode("utf-8").strip())
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def candidates(mode, clients, active_workspace, active_window):
|
||||||
|
current_ws = (active_workspace or {}).get("id")
|
||||||
|
focused = window_address(active_window or {}) if active_window else None
|
||||||
|
filtered = []
|
||||||
|
|
||||||
|
for window in clients:
|
||||||
|
if not normal_workspace(window):
|
||||||
|
continue
|
||||||
|
|
||||||
|
address = window_address(window)
|
||||||
|
if not address:
|
||||||
|
continue
|
||||||
|
|
||||||
|
workspace_id = (window.get("workspace") or {}).get("id")
|
||||||
|
if mode == "bring" and workspace_id == current_ws:
|
||||||
|
continue
|
||||||
|
if mode == "replace" and address == focused:
|
||||||
|
continue
|
||||||
|
|
||||||
|
filtered.append(window)
|
||||||
|
|
||||||
|
filtered.sort(
|
||||||
|
key=lambda window: (
|
||||||
|
(window.get("workspace") or {}).get("id", 9999),
|
||||||
|
window.get("focusHistoryID", 999999),
|
||||||
|
clean_text(window.get("class")).lower(),
|
||||||
|
clean_text(window.get("title")).lower(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return filtered
|
||||||
|
|
||||||
|
|
||||||
|
def menu_entry(window, icons):
|
||||||
|
class_name = clean_text(window.get("class"))
|
||||||
|
title = clean_text(window.get("title"))
|
||||||
|
workspace_id = (window.get("workspace") or {}).get("id", "?")
|
||||||
|
label = f"{class_name[:24]:<24} {title} WS:{workspace_id}"
|
||||||
|
icon = icon_for(icons, class_name)
|
||||||
|
return f"{label}\0icon\x1f{icon}\n"
|
||||||
|
|
||||||
|
|
||||||
|
def focus_window(address):
|
||||||
|
if dispatch_lua(f'hl.dsp.focus({{ window = "address:{address}" }})'):
|
||||||
|
return
|
||||||
|
dispatch_legacy("focuswindow", f"address:{address}")
|
||||||
|
|
||||||
|
|
||||||
|
def move_to_workspace(address, workspace_id):
|
||||||
|
workspace = str(workspace_id)
|
||||||
|
if dispatch_lua(f'hl.dsp.window.move({{ workspace = "{workspace}", window = "address:{address}" }})'):
|
||||||
|
return
|
||||||
|
dispatch_legacy("movetoworkspace", f"{workspace},address:{address}")
|
||||||
|
|
||||||
|
|
||||||
|
def swap_with_focused(target_address, focused_address):
|
||||||
|
if dispatch_lua(
|
||||||
|
f'hl.dsp.window.swap({{ target = "address:{target_address}", window = "address:{focused_address}" }})'
|
||||||
|
):
|
||||||
|
return
|
||||||
|
dispatch_legacy("hy3:movewindow", f"address:{target_address}")
|
||||||
|
|
||||||
|
|
||||||
|
def activate(mode, window, active_workspace, active_window):
|
||||||
|
address = window_address(window)
|
||||||
|
if not address:
|
||||||
|
return
|
||||||
|
|
||||||
|
if mode == "go":
|
||||||
|
focus_window(address)
|
||||||
|
return
|
||||||
|
|
||||||
|
if mode == "bring":
|
||||||
|
current_ws = (active_workspace or {}).get("id")
|
||||||
|
if current_ws is not None:
|
||||||
|
move_to_workspace(address, current_ws)
|
||||||
|
focus_window(address)
|
||||||
|
return
|
||||||
|
|
||||||
|
if mode == "replace":
|
||||||
|
focused = window_address(active_window or {})
|
||||||
|
if focused and focused != address:
|
||||||
|
swap_with_focused(address, focused)
|
||||||
|
focus_window(address)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="Rofi window go/bring/replace for Hyprland.")
|
||||||
|
parser.add_argument("mode", choices=sorted(PROMPTS))
|
||||||
|
parser.add_argument("--print-candidates", action="store_true")
|
||||||
|
parser.add_argument("--select-index", type=int)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
ensure_hyprland_instance()
|
||||||
|
|
||||||
|
clients = run_json("hyprctl", "clients", "-j") or []
|
||||||
|
active_workspace = run_json("hyprctl", "activeworkspace", "-j") or {}
|
||||||
|
active_window = run_json("hyprctl", "activewindow", "-j") or {}
|
||||||
|
windows = candidates(args.mode, clients, active_workspace, active_window)
|
||||||
|
|
||||||
|
if args.print_candidates:
|
||||||
|
print(json.dumps(windows, indent=2, sort_keys=True))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if not windows:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if args.select_index is None:
|
||||||
|
icons = icon_map()
|
||||||
|
entries = [menu_entry(window, icons) for window in windows]
|
||||||
|
index = rofi_index(entries, PROMPTS[args.mode])
|
||||||
|
else:
|
||||||
|
index = args.select_index
|
||||||
|
|
||||||
|
if index is None or index < 0 or index >= len(windows):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
activate(args.mode, windows[index], active_workspace, active_window)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
78
dotfiles/lib/bin/hypr_shell_ui
Executable file
78
dotfiles/lib/bin/hypr_shell_ui
Executable file
@@ -0,0 +1,78 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if command -v desktop_shell_ui >/dev/null 2>&1; then
|
||||||
|
shell_ui="$(desktop_shell_ui current 2>/dev/null || true)"
|
||||||
|
else
|
||||||
|
shell_ui=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
shell_ui="${shell_ui:-${IM_HYPRLAND_SHELL_UI:-taffybar}}"
|
||||||
|
|
||||||
|
run_noctalia() {
|
||||||
|
noctalia-shell ipc --any-display call "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_launcher() {
|
||||||
|
case "$shell_ui" in
|
||||||
|
noctalia)
|
||||||
|
run_noctalia launcher toggle
|
||||||
|
;;
|
||||||
|
taffybar|rofi)
|
||||||
|
exec rofi -show drun -show-icons
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "unknown IM_HYPRLAND_SHELL_UI: $shell_ui" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
run_window_picker() {
|
||||||
|
local mode="${1:-}"
|
||||||
|
|
||||||
|
case "$mode" in
|
||||||
|
go|bring|replace) ;;
|
||||||
|
*)
|
||||||
|
echo "usage: hypr_shell_ui window {go|bring|replace}" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [[ "$shell_ui" == "noctalia" && "${IM_HYPRLAND_NOCTALIA_WINDOW_PICKER:-0}" == "1" ]]; then
|
||||||
|
# Future Noctalia launcher-provider hook. Until that plugin exists or if it
|
||||||
|
# fails to load, keep the existing rofi picker as the working path.
|
||||||
|
run_noctalia "plugin:hypr-window-picker" "$mode" 2>/dev/null && exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec hypr_rofi_window "$mode"
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
launcher)
|
||||||
|
run_launcher
|
||||||
|
;;
|
||||||
|
run)
|
||||||
|
exec rofi -show run
|
||||||
|
;;
|
||||||
|
control-center)
|
||||||
|
if [[ "$shell_ui" == "noctalia" ]]; then
|
||||||
|
run_noctalia controlCenter toggle || true
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
settings)
|
||||||
|
if [[ "$shell_ui" == "noctalia" ]]; then
|
||||||
|
run_noctalia settings toggle || true
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
window)
|
||||||
|
shift
|
||||||
|
run_window_picker "$@"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "usage: hypr_shell_ui {launcher|run|control-center|settings|window}" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
275
dotfiles/lib/bin/roborock-control
Executable file
275
dotfiles/lib/bin/roborock-control
Executable file
@@ -0,0 +1,275 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_PATH = Path.home() / ".config" / "roborock-control" / "config.json"
|
||||||
|
PYTHON_ENV_EXPR = (
|
||||||
|
"with import <nixpkgs> {}; "
|
||||||
|
"python313.withPackages (ps: [ ps.python-roborock ps.pyyaml ps.pyshark ])"
|
||||||
|
)
|
||||||
|
COMMON_COMMANDS = {
|
||||||
|
"start": ("app_start", None),
|
||||||
|
"pause": ("app_pause", None),
|
||||||
|
"stop": ("app_stop", None),
|
||||||
|
"dock": ("app_charge", None),
|
||||||
|
"find": ("find_me", None),
|
||||||
|
"dust": ("app_start_collect_dust", None),
|
||||||
|
"stop-dust": ("app_stop_collect_dust", None),
|
||||||
|
"wash": ("app_start_wash", None),
|
||||||
|
"stop-wash": ("app_stop_wash", None),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def load_config():
|
||||||
|
if not CONFIG_PATH.exists():
|
||||||
|
return {}
|
||||||
|
return json.loads(CONFIG_PATH.read_text())
|
||||||
|
|
||||||
|
|
||||||
|
def save_config(config):
|
||||||
|
CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
CONFIG_PATH.write_text(json.dumps(config, indent=2, sort_keys=True) + "\n")
|
||||||
|
|
||||||
|
|
||||||
|
def roborock_args(*args):
|
||||||
|
if os.getenv("ROBOROCK_CONTROL_RUNNER") == "direct":
|
||||||
|
return ["roborock", *args]
|
||||||
|
return [
|
||||||
|
"nix",
|
||||||
|
"shell",
|
||||||
|
"--impure",
|
||||||
|
"--expr",
|
||||||
|
PYTHON_ENV_EXPR,
|
||||||
|
"--command",
|
||||||
|
"roborock",
|
||||||
|
*args,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def run_roborock(*args, capture=False, check=True):
|
||||||
|
kwargs = {
|
||||||
|
"text": True,
|
||||||
|
"check": check,
|
||||||
|
}
|
||||||
|
if capture:
|
||||||
|
kwargs["stdout"] = subprocess.PIPE
|
||||||
|
kwargs["stderr"] = subprocess.PIPE
|
||||||
|
return subprocess.run(roborock_args(*args), **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def configured_device_id(args, config):
|
||||||
|
return args.device_id or os.getenv("ROBOROCK_DEVICE_ID") or config.get("device_id")
|
||||||
|
|
||||||
|
|
||||||
|
def infer_single_device_id():
|
||||||
|
result = run_roborock("list-devices", capture=True)
|
||||||
|
devices = json.loads(result.stdout)
|
||||||
|
if len(devices) != 1:
|
||||||
|
names = ", ".join(sorted(devices)) or "none"
|
||||||
|
raise SystemExit(
|
||||||
|
"Could not infer a default device. "
|
||||||
|
f"Found {len(devices)} devices: {names}. "
|
||||||
|
"Pass --device-id or run 'roborock-control config --device-id <id>'."
|
||||||
|
)
|
||||||
|
return next(iter(devices.values()))
|
||||||
|
|
||||||
|
|
||||||
|
def get_device_id(args, config):
|
||||||
|
return configured_device_id(args, config) or infer_single_device_id()
|
||||||
|
|
||||||
|
|
||||||
|
def handle_config(args):
|
||||||
|
config = load_config()
|
||||||
|
changed = False
|
||||||
|
if args.clear:
|
||||||
|
config = {}
|
||||||
|
changed = True
|
||||||
|
if args.device_id is not None:
|
||||||
|
config["device_id"] = args.device_id
|
||||||
|
changed = True
|
||||||
|
if args.email is not None:
|
||||||
|
config["email"] = args.email
|
||||||
|
changed = True
|
||||||
|
if changed:
|
||||||
|
save_config(config)
|
||||||
|
print(json.dumps(config, indent=2, sort_keys=True))
|
||||||
|
|
||||||
|
|
||||||
|
def handle_login(args):
|
||||||
|
config = load_config()
|
||||||
|
email = args.email or os.getenv("ROBOROCK_EMAIL") or config.get("email")
|
||||||
|
if not email:
|
||||||
|
raise SystemExit(
|
||||||
|
"Email is required. Pass --email or run "
|
||||||
|
"'roborock-control config --email <address>'."
|
||||||
|
)
|
||||||
|
cli_args = ["login", "--email", email]
|
||||||
|
if args.reauth:
|
||||||
|
cli_args.append("--reauth")
|
||||||
|
if args.password_command:
|
||||||
|
password = subprocess.check_output(args.password_command, shell=True, text=True).strip()
|
||||||
|
cli_args.extend(["--password", password])
|
||||||
|
run_roborock(*cli_args)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_devices(args):
|
||||||
|
if args.refresh:
|
||||||
|
run_roborock("discover")
|
||||||
|
run_roborock("list-devices")
|
||||||
|
|
||||||
|
|
||||||
|
def command_with_device(args, upstream_command):
|
||||||
|
config = load_config()
|
||||||
|
return [upstream_command, "--device_id", get_device_id(args, config)]
|
||||||
|
|
||||||
|
|
||||||
|
def handle_home(args):
|
||||||
|
cli_args = command_with_device(args, "home")
|
||||||
|
if args.refresh:
|
||||||
|
cli_args.append("--refresh")
|
||||||
|
run_roborock(*cli_args)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_maps(args):
|
||||||
|
run_roborock(*command_with_device(args, "maps"))
|
||||||
|
|
||||||
|
|
||||||
|
def handle_rooms(args):
|
||||||
|
run_roborock(*command_with_device(args, "rooms"))
|
||||||
|
|
||||||
|
|
||||||
|
def handle_map_data(args):
|
||||||
|
cli_args = command_with_device(args, "map-data")
|
||||||
|
if args.include_path:
|
||||||
|
cli_args.append("--include_path")
|
||||||
|
run_roborock(*cli_args)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_map_image(args):
|
||||||
|
run_roborock(
|
||||||
|
*command_with_device(args, "map-image"),
|
||||||
|
"--output-file",
|
||||||
|
args.output_file,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_command(args):
|
||||||
|
config = load_config()
|
||||||
|
params = args.params
|
||||||
|
cli_args = [
|
||||||
|
"command",
|
||||||
|
"--device_id",
|
||||||
|
get_device_id(args, config),
|
||||||
|
"--cmd",
|
||||||
|
args.command_name,
|
||||||
|
]
|
||||||
|
if params is not None:
|
||||||
|
cli_args.extend(["--params", params])
|
||||||
|
run_roborock(*cli_args)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_common_command(args):
|
||||||
|
command_name, params = COMMON_COMMANDS[args.action]
|
||||||
|
config = load_config()
|
||||||
|
cli_args = [
|
||||||
|
"command",
|
||||||
|
"--device_id",
|
||||||
|
get_device_id(args, config),
|
||||||
|
"--cmd",
|
||||||
|
command_name,
|
||||||
|
]
|
||||||
|
if params is not None:
|
||||||
|
cli_args.extend(["--params", params])
|
||||||
|
run_roborock(*cli_args)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_status(args):
|
||||||
|
config = load_config()
|
||||||
|
run_roborock("status", "--device_id", get_device_id(args, config))
|
||||||
|
|
||||||
|
|
||||||
|
def handle_upstream(args):
|
||||||
|
run_roborock(*args.args)
|
||||||
|
|
||||||
|
|
||||||
|
def build_parser():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Control Roborock vacuums through python-roborock."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--device-id",
|
||||||
|
help="Roborock device id. Defaults to ROBOROCK_DEVICE_ID, saved config, or the only cached device.",
|
||||||
|
)
|
||||||
|
subparsers = parser.add_subparsers(dest="subcommand", required=True)
|
||||||
|
|
||||||
|
config_parser = subparsers.add_parser("config", help="Show or update saved defaults")
|
||||||
|
config_parser.add_argument("--device-id")
|
||||||
|
config_parser.add_argument("--email")
|
||||||
|
config_parser.add_argument("--clear", action="store_true")
|
||||||
|
config_parser.set_defaults(func=handle_config)
|
||||||
|
|
||||||
|
login_parser = subparsers.add_parser("login", help="Login with email code or password")
|
||||||
|
login_parser.add_argument("--email")
|
||||||
|
login_parser.add_argument("--reauth", action="store_true")
|
||||||
|
login_parser.add_argument(
|
||||||
|
"--password-command",
|
||||||
|
help="Shell command that prints the Roborock password, e.g. 'pass show path/to/login'.",
|
||||||
|
)
|
||||||
|
login_parser.set_defaults(func=handle_login)
|
||||||
|
|
||||||
|
devices_parser = subparsers.add_parser("devices", help="List cached devices")
|
||||||
|
devices_parser.add_argument("--refresh", action="store_true", help="Refresh discovery first")
|
||||||
|
devices_parser.set_defaults(func=handle_devices)
|
||||||
|
|
||||||
|
subparsers.add_parser("status", help="Show vacuum status").set_defaults(func=handle_status)
|
||||||
|
|
||||||
|
home_parser = subparsers.add_parser("home", help="Discover and cache home layout")
|
||||||
|
home_parser.add_argument("--refresh", action="store_true")
|
||||||
|
home_parser.set_defaults(func=handle_home)
|
||||||
|
|
||||||
|
subparsers.add_parser("maps", help="Show map metadata").set_defaults(func=handle_maps)
|
||||||
|
subparsers.add_parser("rooms", help="Show room metadata").set_defaults(func=handle_rooms)
|
||||||
|
|
||||||
|
map_data_parser = subparsers.add_parser("map-data", help="Show parsed map data as JSON")
|
||||||
|
map_data_parser.add_argument("--include-path", action="store_true")
|
||||||
|
map_data_parser.set_defaults(func=handle_map_data)
|
||||||
|
|
||||||
|
map_image_parser = subparsers.add_parser("map-image", help="Save the map image")
|
||||||
|
map_image_parser.add_argument("output_file")
|
||||||
|
map_image_parser.set_defaults(func=handle_map_image)
|
||||||
|
|
||||||
|
raw_parser = subparsers.add_parser("raw", help="Send a raw python-roborock command")
|
||||||
|
raw_parser.add_argument("command_name")
|
||||||
|
raw_parser.add_argument("params", nargs="?")
|
||||||
|
raw_parser.set_defaults(func=handle_command)
|
||||||
|
|
||||||
|
upstream_parser = subparsers.add_parser("cli", help="Pass arguments to the upstream CLI")
|
||||||
|
upstream_parser.add_argument("args", nargs=argparse.REMAINDER)
|
||||||
|
upstream_parser.set_defaults(func=handle_upstream)
|
||||||
|
|
||||||
|
for action in sorted(COMMON_COMMANDS):
|
||||||
|
subparsers.add_parser(action).set_defaults(func=handle_common_command, action=action)
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = build_parser()
|
||||||
|
args = parser.parse_args()
|
||||||
|
try:
|
||||||
|
args.func(args)
|
||||||
|
except subprocess.CalledProcessError as error:
|
||||||
|
if error.stdout:
|
||||||
|
print(error.stdout, end="")
|
||||||
|
if error.stderr:
|
||||||
|
print(error.stderr, end="", file=sys.stderr)
|
||||||
|
raise SystemExit(error.returncode)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -69,6 +69,7 @@
|
|||||||
multiplexerAliases = import ../../shared/multiplexer-aliases.nix;
|
multiplexerAliases = import ../../shared/multiplexer-aliases.nix;
|
||||||
|
|
||||||
excludedTopLevelEntries = [
|
excludedTopLevelEntries = [
|
||||||
|
"codex"
|
||||||
"config"
|
"config"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,10 @@
|
|||||||
...
|
...
|
||||||
}: let
|
}: let
|
||||||
cfg = config.myModules.codexGeneratedSkills;
|
cfg = config.myModules.codexGeneratedSkills;
|
||||||
|
oos = config.lib.file.mkOutOfStoreSymlink;
|
||||||
in {
|
in {
|
||||||
options.myModules.codexGeneratedSkills = {
|
options.myModules.codexGeneratedSkills = {
|
||||||
enable = lib.mkEnableOption "generated Codex skill setup";
|
enable = lib.mkEnableOption "Codex home setup";
|
||||||
|
|
||||||
codexHome = lib.mkOption {
|
codexHome = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
@@ -15,6 +16,12 @@ in {
|
|||||||
description = "Codex home directory.";
|
description = "Codex home directory.";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
worktreeCodexDir = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "${config.home.homeDirectory}/dotfiles/dotfiles/codex";
|
||||||
|
description = "Codex dotfiles directory in the live worktree.";
|
||||||
|
};
|
||||||
|
|
||||||
skillsDir = lib.mkOption {
|
skillsDir = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
default = "${cfg.codexHome}/skills";
|
default = "${cfg.codexHome}/skills";
|
||||||
@@ -29,6 +36,67 @@ in {
|
|||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
|
home.file = {
|
||||||
|
".codex/.gitignore" = {
|
||||||
|
force = true;
|
||||||
|
source = oos "${cfg.worktreeCodexDir}/.gitignore";
|
||||||
|
};
|
||||||
|
|
||||||
|
".codex/AGENTS.md" = {
|
||||||
|
force = true;
|
||||||
|
source = oos "${cfg.worktreeCodexDir}/AGENTS.md";
|
||||||
|
};
|
||||||
|
|
||||||
|
".codex/skills" = {
|
||||||
|
force = true;
|
||||||
|
source = oos "${cfg.worktreeCodexDir}/skills";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
home.activation.prepareCodexDirectory = lib.hm.dag.entryBefore ["checkLinkTargets"] ''
|
||||||
|
codex_home=${lib.escapeShellArg cfg.codexHome}
|
||||||
|
worktree_codex=${lib.escapeShellArg cfg.worktreeCodexDir}
|
||||||
|
|
||||||
|
if [ -L "$codex_home" ] && [ "$(readlink "$codex_home")" = "$worktree_codex" ]; then
|
||||||
|
rm -f "$codex_home"
|
||||||
|
mkdir -p "$codex_home"
|
||||||
|
elif [ ! -e "$codex_home" ]; then
|
||||||
|
mkdir -p "$codex_home"
|
||||||
|
elif [ ! -d "$codex_home" ]; then
|
||||||
|
echo "Skipping Codex setup because $codex_home is not a directory" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
|
||||||
|
home.activation.generateCodexConfig = lib.hm.dag.entryAfter ["writeBoundary"] ''
|
||||||
|
codex_home=${lib.escapeShellArg cfg.codexHome}
|
||||||
|
base=${lib.escapeShellArg "${cfg.worktreeCodexDir}/config.toml"}
|
||||||
|
local_config=${lib.escapeShellArg "${cfg.worktreeCodexDir}/config.local.toml"}
|
||||||
|
target="$codex_home/config.toml"
|
||||||
|
|
||||||
|
if [ ! -r "$base" ]; then
|
||||||
|
echo "Missing shared Codex config at $base" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$codex_home"
|
||||||
|
tmp="$(mktemp "$codex_home/config.toml.XXXXXX")"
|
||||||
|
trap 'rm -f "$tmp"' EXIT
|
||||||
|
chmod 600 "$tmp"
|
||||||
|
|
||||||
|
cat "$base" > "$tmp"
|
||||||
|
if [ -r "$local_config" ]; then
|
||||||
|
printf '\n' >> "$tmp"
|
||||||
|
cat "$local_config" >> "$tmp"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -e "$target" ] && cmp -s "$tmp" "$target"; then
|
||||||
|
rm -f "$tmp"
|
||||||
|
else
|
||||||
|
mv -f "$tmp" "$target"
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
|
||||||
home.activation.setupCodexGeneratedSkills = lib.hm.dag.entryAfter ["writeBoundary"] ''
|
home.activation.setupCodexGeneratedSkills = lib.hm.dag.entryAfter ["writeBoundary"] ''
|
||||||
codex_home=${lib.escapeShellArg cfg.codexHome}
|
codex_home=${lib.escapeShellArg cfg.codexHome}
|
||||||
skills_dir=${lib.escapeShellArg cfg.skillsDir}
|
skills_dir=${lib.escapeShellArg cfg.skillsDir}
|
||||||
|
|||||||
@@ -29,12 +29,14 @@
|
|||||||
./laptop.nix
|
./laptop.nix
|
||||||
./nix.nix
|
./nix.nix
|
||||||
./notifications-tray-icon.nix
|
./notifications-tray-icon.nix
|
||||||
|
./noctalia.nix
|
||||||
./nvidia.nix
|
./nvidia.nix
|
||||||
./options.nix
|
./options.nix
|
||||||
./plasma.nix
|
./plasma.nix
|
||||||
./postgres.nix
|
./postgres.nix
|
||||||
./rabbitmq.nix
|
./rabbitmq.nix
|
||||||
./quickshell.nix
|
./quickshell.nix
|
||||||
|
./remote-hyprland.nix
|
||||||
./secrets.nix
|
./secrets.nix
|
||||||
./ssh.nix
|
./ssh.nix
|
||||||
./sni.nix
|
./sni.nix
|
||||||
|
|||||||
@@ -6,7 +6,20 @@
|
|||||||
makeEnable,
|
makeEnable,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
makeEnable config "myModules.desktop" true {
|
let
|
||||||
|
cfg = config.myModules.desktop;
|
||||||
|
desktopShellUi = pkgs.writeShellApplication {
|
||||||
|
name = "desktop_shell_ui";
|
||||||
|
runtimeInputs = [
|
||||||
|
pkgs.bash
|
||||||
|
pkgs.coreutils
|
||||||
|
pkgs.systemd
|
||||||
|
];
|
||||||
|
text = ''
|
||||||
|
exec ${../dotfiles/lib/bin/desktop_shell_ui} "$@"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
enabledModule = makeEnable config "myModules.desktop" true {
|
||||||
services.greenclip.enable = true;
|
services.greenclip.enable = true;
|
||||||
imports = [
|
imports = [
|
||||||
./fonts.nix
|
./fonts.nix
|
||||||
@@ -40,8 +53,11 @@ makeEnable config "myModules.desktop" true {
|
|||||||
enable = true;
|
enable = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
environment.sessionVariables = {
|
||||||
# This is for the benefit of VSCODE running natively in wayland
|
# This is for the benefit of VSCODE running natively in wayland
|
||||||
environment.sessionVariables.NIXOS_OZONE_WL = "1";
|
NIXOS_OZONE_WL = "1";
|
||||||
|
IM_HYPRLAND_SHELL_UI = cfg.shellUi;
|
||||||
|
};
|
||||||
|
|
||||||
system.activationScripts.playwrightChromeCompat.text = lib.optionalString (pkgs.stdenv.hostPlatform.system == "x86_64-linux") ''
|
system.activationScripts.playwrightChromeCompat.text = lib.optionalString (pkgs.stdenv.hostPlatform.system == "x86_64-linux") ''
|
||||||
# Playwright's Chrome channel lookup expects the FHS path below.
|
# Playwright's Chrome channel lookup expects the FHS path below.
|
||||||
@@ -87,6 +103,8 @@ makeEnable config "myModules.desktop" true {
|
|||||||
|
|
||||||
environment.systemPackages = with pkgs;
|
environment.systemPackages = with pkgs;
|
||||||
[
|
[
|
||||||
|
desktopShellUi
|
||||||
|
|
||||||
# Appearance
|
# Appearance
|
||||||
adwaita-icon-theme
|
adwaita-icon-theme
|
||||||
hicolor-icon-theme
|
hicolor-icon-theme
|
||||||
@@ -169,4 +187,18 @@ makeEnable config "myModules.desktop" true {
|
|||||||
]
|
]
|
||||||
else []
|
else []
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
in
|
||||||
|
enabledModule
|
||||||
|
// {
|
||||||
|
options = lib.recursiveUpdate enabledModule.options {
|
||||||
|
myModules.desktop.shellUi = lib.mkOption {
|
||||||
|
type = lib.types.enum [ "noctalia" "taffybar" ];
|
||||||
|
default = "taffybar";
|
||||||
|
description = ''
|
||||||
|
Desktop shell UI used by Hyprland-oriented bindings. This controls
|
||||||
|
the active shell service and Hyprland launcher/window picker dispatch.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
{ config, lib, ... }:
|
{
|
||||||
let
|
config,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
# Replicate the useful part of rcm/rcup:
|
# Replicate the useful part of rcm/rcup:
|
||||||
# - dotfiles live in ~/dotfiles/dotfiles (no leading dots in the repo)
|
# - dotfiles live in ~/dotfiles/dotfiles (no leading dots in the repo)
|
||||||
# - links in $HOME add a leading '.' to the first path component
|
# - links in $HOME add a leading '.' to the first path component
|
||||||
@@ -16,6 +19,9 @@ let
|
|||||||
srcDotfiles = ../dotfiles;
|
srcDotfiles = ../dotfiles;
|
||||||
|
|
||||||
excludedTop = [
|
excludedTop = [
|
||||||
|
# Managed by nix-shared/home-manager/codex-generated-skills.nix so
|
||||||
|
# config.toml can be generated from shared and machine-local fragments.
|
||||||
|
"codex"
|
||||||
# Managed by Nix directly (PATH/fpath), not meant to appear as ~/.lib.
|
# Managed by Nix directly (PATH/fpath), not meant to appear as ~/.lib.
|
||||||
"lib"
|
"lib"
|
||||||
# Avoid colliding with HM-generated xdg.configFile entries for now.
|
# Avoid colliding with HM-generated xdg.configFile entries for now.
|
||||||
@@ -24,25 +30,23 @@ let
|
|||||||
"emacs.d"
|
"emacs.d"
|
||||||
];
|
];
|
||||||
|
|
||||||
firstComponent = rel:
|
firstComponent = rel: let
|
||||||
let parts = lib.splitString "/" rel;
|
parts = lib.splitString "/" rel;
|
||||||
in lib.elemAt parts 0;
|
in
|
||||||
|
lib.elemAt parts 0;
|
||||||
|
|
||||||
isExcluded = rel: lib.elem (firstComponent rel) excludedTop;
|
isExcluded = rel: lib.elem (firstComponent rel) excludedTop;
|
||||||
|
|
||||||
listFilesRec = dir:
|
listFilesRec = dir: let
|
||||||
let
|
|
||||||
entries = builtins.readDir dir;
|
entries = builtins.readDir dir;
|
||||||
names = builtins.attrNames entries;
|
names = builtins.attrNames entries;
|
||||||
go = name:
|
go = name: let
|
||||||
let
|
|
||||||
ty = entries.${name};
|
ty = entries.${name};
|
||||||
path = dir + "/${name}";
|
path = dir + "/${name}";
|
||||||
in
|
in
|
||||||
if ty == "directory" then
|
if ty == "directory"
|
||||||
map (p: "${name}/${p}") (listFilesRec path)
|
then map (p: "${name}/${p}") (listFilesRec path)
|
||||||
else
|
else [name];
|
||||||
[ name ];
|
|
||||||
in
|
in
|
||||||
lib.concatLists (map go names);
|
lib.concatLists (map go names);
|
||||||
|
|
||||||
@@ -53,9 +57,10 @@ let
|
|||||||
lib.nameValuePair ".${rel}" {
|
lib.nameValuePair ".${rel}" {
|
||||||
source = oos "${worktreeDotfiles}/${rel}";
|
source = oos "${worktreeDotfiles}/${rel}";
|
||||||
};
|
};
|
||||||
in
|
in {
|
||||||
{
|
imports = [
|
||||||
imports = [ ../nix-shared/home-manager/codex-generated-skills.nix ];
|
../nix-shared/home-manager/codex-generated-skills.nix
|
||||||
|
];
|
||||||
|
|
||||||
home.file =
|
home.file =
|
||||||
builtins.listToAttrs (map mkManaged managedRelFiles);
|
builtins.listToAttrs (map mkManaged managedRelFiles);
|
||||||
@@ -74,5 +79,4 @@ in
|
|||||||
echo "Skipping ~/.emacs.d relink because it is not a symlink" >&2
|
echo "Skipping ~/.emacs.d relink because it is not a symlink" >&2
|
||||||
fi
|
fi
|
||||||
'';
|
'';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ makeEnable config "myModules.extra" false {
|
|||||||
signal-desktop
|
signal-desktop
|
||||||
gource
|
gource
|
||||||
gimp
|
gimp
|
||||||
|
kef
|
||||||
|
roborock-control
|
||||||
texlive.combined.scheme-full
|
texlive.combined.scheme-full
|
||||||
tor
|
tor
|
||||||
yt-dlp
|
yt-dlp
|
||||||
|
|||||||
825
nixos/flake.lock
generated
825
nixos/flake.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -93,35 +93,25 @@
|
|||||||
|
|
||||||
# Hyprland and plugins from official flakes for proper plugin compatibility
|
# Hyprland and plugins from official flakes for proper plugin compatibility
|
||||||
hyprland = {
|
hyprland = {
|
||||||
url = "git+https://github.com/hyprwm/Hyprland?submodules=1&ref=refs/tags/v0.53.0";
|
url = "git+https://github.com/hyprwm/Hyprland?submodules=1";
|
||||||
};
|
|
||||||
|
|
||||||
hyprland-lua-config = {
|
|
||||||
# Experimental Lua config branch from PR 13817.
|
|
||||||
url = "git+https://github.com/hyprwm/Hyprland?submodules=1&ref=refs/pull/13817/head";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
hyprNStack = {
|
hyprNStack = {
|
||||||
url = "github:colonelpanic8/hyprNStack";
|
url = "github:colonelpanic8/hyprNStack?ref=hyprland-lua-integration";
|
||||||
inputs = {
|
inputs = {
|
||||||
hyprland.follows = "hyprland-lua-config";
|
hyprland.follows = "hyprland";
|
||||||
nixpkgs.follows = "nixpkgs";
|
nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
hy3 = {
|
|
||||||
url = "github:outfoxxed/hy3?ref=hl0.53.0";
|
|
||||||
inputs.hyprland.follows = "hyprland";
|
|
||||||
};
|
|
||||||
|
|
||||||
hyprland-plugins = {
|
|
||||||
url = "github:hyprwm/hyprland-plugins?ref=v0.53.0";
|
|
||||||
inputs.hyprland.follows = "hyprland";
|
|
||||||
};
|
|
||||||
|
|
||||||
hyprland-plugins-lua = {
|
hyprland-plugins-lua = {
|
||||||
url = "github:colonelpanic8/hyprland-plugins?ref=hyprexpo-lua-hyprland";
|
url = "github:colonelpanic8/hyprland-plugins?ref=codex/fix-main-ci";
|
||||||
inputs.hyprland.follows = "hyprland-lua-config";
|
inputs.hyprland.follows = "hyprland";
|
||||||
|
};
|
||||||
|
|
||||||
|
hyprwinview = {
|
||||||
|
url = "github:colonelpanic8/hyprwinview";
|
||||||
|
inputs.hyprland.follows = "hyprland";
|
||||||
};
|
};
|
||||||
|
|
||||||
hyprscratch = {
|
hyprscratch = {
|
||||||
@@ -161,26 +151,11 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
taffybar = {
|
|
||||||
# Use a remote, lockfile-pinned input so rebuilds are reproducible across
|
|
||||||
# machines. For local development, use `nixos-rebuild --override-input taffybar path:...`.
|
|
||||||
url = "github:taffybar/taffybar";
|
|
||||||
inputs = {
|
|
||||||
nixpkgs.follows = "nixpkgs";
|
|
||||||
flake-utils.follows = "flake-utils";
|
|
||||||
xmonad.follows = "xmonad";
|
|
||||||
xmonad-contrib.follows = "xmonad-contrib";
|
|
||||||
weeder-nix.url = "github:NorfairKing/weeder-nix";
|
|
||||||
weeder-nix.inputs.pre-commit-hooks.inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
imalison-taffybar = {
|
imalison-taffybar = {
|
||||||
url = "path:../dotfiles/config/taffybar";
|
url = "path:../dotfiles/config/taffybar";
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.follows = "nixpkgs";
|
nixpkgs.follows = "nixpkgs";
|
||||||
flake-utils.follows = "flake-utils";
|
flake-utils.follows = "flake-utils";
|
||||||
taffybar.follows = "taffybar";
|
|
||||||
xmonad.follows = "xmonad";
|
xmonad.follows = "xmonad";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -231,6 +206,11 @@
|
|||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
noctalia = {
|
||||||
|
url = "github:noctalia-dev/noctalia-shell";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = inputs @ {
|
outputs = inputs @ {
|
||||||
@@ -238,7 +218,6 @@
|
|||||||
nixpkgs,
|
nixpkgs,
|
||||||
nixos-hardware,
|
nixos-hardware,
|
||||||
home-manager,
|
home-manager,
|
||||||
taffybar,
|
|
||||||
xmonad,
|
xmonad,
|
||||||
nixtheplanet,
|
nixtheplanet,
|
||||||
xmonad-contrib,
|
xmonad-contrib,
|
||||||
@@ -247,8 +226,6 @@
|
|||||||
agenix,
|
agenix,
|
||||||
imalison-taffybar,
|
imalison-taffybar,
|
||||||
hyprland,
|
hyprland,
|
||||||
hy3,
|
|
||||||
hyprland-plugins,
|
|
||||||
org-agenda-api,
|
org-agenda-api,
|
||||||
flake-utils,
|
flake-utils,
|
||||||
...
|
...
|
||||||
@@ -443,6 +420,7 @@
|
|||||||
"https://colonelpanic8-dotfiles.cachix.org"
|
"https://colonelpanic8-dotfiles.cachix.org"
|
||||||
"https://codex-cli.cachix.org"
|
"https://codex-cli.cachix.org"
|
||||||
"https://claude-code.cachix.org"
|
"https://claude-code.cachix.org"
|
||||||
|
"https://noctalia.cachix.org"
|
||||||
];
|
];
|
||||||
extra-trusted-substituters = [
|
extra-trusted-substituters = [
|
||||||
"https://ai.cachix.org"
|
"https://ai.cachix.org"
|
||||||
@@ -464,6 +442,7 @@
|
|||||||
"colonelpanic8-dotfiles.cachix.org-1:O6GF3nptpeMFapX29okzO92eSWXR36zqW6ZF2C8P0eQ="
|
"colonelpanic8-dotfiles.cachix.org-1:O6GF3nptpeMFapX29okzO92eSWXR36zqW6ZF2C8P0eQ="
|
||||||
"codex-cli.cachix.org-1:1Br3H1hHoRYG22n//cGKJOk3cQXgYobUel6O8DgSing="
|
"codex-cli.cachix.org-1:1Br3H1hHoRYG22n//cGKJOk3cQXgYobUel6O8DgSing="
|
||||||
"claude-code.cachix.org-1:YeXf2aNu7UTX8Vwrze0za1WEDS+4DuI2kVeWEE4fsRk="
|
"claude-code.cachix.org-1:YeXf2aNu7UTX8Vwrze0za1WEDS+4DuI2kVeWEE4fsRk="
|
||||||
|
"noctalia.cachix.org-1:pCOR47nnMEo5thcxNDtzWpOxNFQsBRglJzxWPp3dkU4="
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
nixosConfigurations =
|
nixosConfigurations =
|
||||||
@@ -497,6 +476,26 @@
|
|||||||
containerLib = import ../org-agenda-api/container.nix {
|
containerLib = import ../org-agenda-api/container.nix {
|
||||||
inherit pkgs system tangledConfig org-agenda-api orgApiRev dotfilesRev;
|
inherit pkgs system tangledConfig org-agenda-api orgApiRev dotfilesRev;
|
||||||
};
|
};
|
||||||
|
hyprlandPkgs = import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
overlays = [ hyprland.overlays.hyprland-packages ];
|
||||||
|
};
|
||||||
|
hyprWorkspaceHistory = hyprlandPkgs.hyprlandPlugins.mkHyprlandPlugin {
|
||||||
|
pluginName = "hypr-workspace-history";
|
||||||
|
version = "0.1.0";
|
||||||
|
src = builtins.path {
|
||||||
|
path = ../dotfiles/config/hypr/workspace-history-plugin;
|
||||||
|
name = "hypr-workspace-history-source";
|
||||||
|
};
|
||||||
|
|
||||||
|
inherit (hyprland.packages.${system}.hyprland) nativeBuildInputs;
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
description = "Workspace history cycling plugin for Hyprland";
|
||||||
|
license = lib.licenses.bsd3;
|
||||||
|
platforms = lib.platforms.linux;
|
||||||
|
};
|
||||||
|
};
|
||||||
in {
|
in {
|
||||||
packages = {
|
packages = {
|
||||||
colonelpanic-org-agenda-api = containerLib.containers.colonelpanic;
|
colonelpanic-org-agenda-api = containerLib.containers.colonelpanic;
|
||||||
@@ -504,16 +503,21 @@
|
|||||||
} // lib.optionalAttrs pkgs.stdenv.isLinux {
|
} // lib.optionalAttrs pkgs.stdenv.isLinux {
|
||||||
hyprNStack = inputs.hyprNStack.packages.${system}.hyprNStack;
|
hyprNStack = inputs.hyprNStack.packages.${system}.hyprNStack;
|
||||||
hyprexpo-lua = inputs.hyprland-plugins-lua.packages.${system}.hyprexpo;
|
hyprexpo-lua = inputs.hyprland-plugins-lua.packages.${system}.hyprexpo;
|
||||||
|
hyprwinview = inputs.hyprwinview.packages.${system}.hyprwinview;
|
||||||
|
hypr-workspace-history = hyprWorkspaceHistory;
|
||||||
};
|
};
|
||||||
|
|
||||||
checks = lib.optionalAttrs pkgs.stdenv.isLinux {
|
checks = lib.optionalAttrs pkgs.stdenv.isLinux {
|
||||||
hyprNStack = inputs.hyprNStack.packages.${system}.hyprNStack;
|
hyprNStack = inputs.hyprNStack.packages.${system}.hyprNStack;
|
||||||
hyprexpo-lua = inputs.hyprland-plugins-lua.packages.${system}.hyprexpo;
|
hyprexpo-lua = inputs.hyprland-plugins-lua.packages.${system}.hyprexpo;
|
||||||
hyprland-lua-config-syntax = pkgs.runCommand "hyprland-lua-config-syntax" {
|
hyprwinview = inputs.hyprwinview.packages.${system}.hyprwinview;
|
||||||
|
hypr-workspace-history = hyprWorkspaceHistory;
|
||||||
|
hyprland-config-syntax = pkgs.runCommand "hyprland-config-syntax" {
|
||||||
nativeBuildInputs = [ pkgs.lua5_4 ];
|
nativeBuildInputs = [ pkgs.lua5_4 ];
|
||||||
} ''
|
} ''
|
||||||
luac -p ${../dotfiles/config/hypr/hyprland.lua}
|
cp ${../dotfiles/config/hypr/hyprland.lua} hyprland.lua
|
||||||
if grep -n 'hyprctl' ${../dotfiles/config/hypr/hyprland.lua} | grep -v 'hyprctl reload'; then
|
luac -p hyprland.lua
|
||||||
|
if grep -n 'hyprctl' hyprland.lua | grep -v 'hyprctl reload' | grep -v 'hyprctl dispatch hyprwinview:overview'; then
|
||||||
echo "hyprland.lua should not shell out to hyprctl for window/workspace manipulation" >&2
|
echo "hyprland.lua should not shell out to hyprctl for window/workspace manipulation" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
@@ -622,7 +626,7 @@
|
|||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
dofile("${../dotfiles/config/hypr/hyprland.lua}")
|
dofile("./hyprland.lua")
|
||||||
|
|
||||||
for _, callback in ipairs(callbacks) do
|
for _, callback in ipairs(callbacks) do
|
||||||
callback()
|
callback()
|
||||||
@@ -630,8 +634,8 @@
|
|||||||
LUA
|
LUA
|
||||||
touch "$out"
|
touch "$out"
|
||||||
'';
|
'';
|
||||||
hyprland-lua-verify-config = let
|
hyprland-verify-config = let
|
||||||
hyprlandPackage = inputs.hyprland-lua-config.packages.${system}.hyprland;
|
hyprlandPackage = inputs.hyprland.packages.${system}.hyprland;
|
||||||
hyprNStackPackage = inputs.hyprNStack.packages.${system}.hyprNStack;
|
hyprNStackPackage = inputs.hyprNStack.packages.${system}.hyprNStack;
|
||||||
in pkgs.runCommand "hyprland-lua-verify-config" {} ''
|
in pkgs.runCommand "hyprland-lua-verify-config" {} ''
|
||||||
cp ${../dotfiles/config/hypr/hyprland.lua} hyprland.lua
|
cp ${../dotfiles/config/hypr/hyprland.lua} hyprland.lua
|
||||||
|
|||||||
@@ -6,33 +6,6 @@
|
|||||||
...
|
...
|
||||||
}: let
|
}: let
|
||||||
mimeMap = desktopId: mimeTypes: lib.genAttrs mimeTypes (_: [desktopId]);
|
mimeMap = desktopId: mimeTypes: lib.genAttrs mimeTypes (_: [desktopId]);
|
||||||
in {
|
|
||||||
# Automatic garbage collection of old home-manager generations
|
|
||||||
nix.gc = {
|
|
||||||
automatic = true;
|
|
||||||
dates = "weekly";
|
|
||||||
options = "--delete-older-than 7d";
|
|
||||||
};
|
|
||||||
|
|
||||||
xdg.configFile."greenclip.toml".text = ''
|
|
||||||
[greenclip]
|
|
||||||
history_file = "~/.cache/greenclip.history"
|
|
||||||
max_history_length = 50
|
|
||||||
max_selection_size_bytes = 0
|
|
||||||
trim_space_from_selection = true
|
|
||||||
use_primary_selection_as_input = false
|
|
||||||
blacklisted_applications = []
|
|
||||||
enable_image_support = true
|
|
||||||
image_cache_directory = "~/.cache/greenclip"
|
|
||||||
static_history = []
|
|
||||||
'';
|
|
||||||
|
|
||||||
xdg.configFile."zellij/config.kdl".source =
|
|
||||||
config.lib.file.mkOutOfStoreSymlink
|
|
||||||
"${config.home.homeDirectory}/dotfiles/dotfiles/config/zellij/config.kdl";
|
|
||||||
|
|
||||||
xdg.mimeApps = lib.mkIf nixos.config.myModules.desktop.enable (
|
|
||||||
let
|
|
||||||
browser = "google-chrome.desktop";
|
browser = "google-chrome.desktop";
|
||||||
imageViewer = "org.kde.gwenview.desktop";
|
imageViewer = "org.kde.gwenview.desktop";
|
||||||
pdfViewer = "okularApplication_pdf.desktop";
|
pdfViewer = "okularApplication_pdf.desktop";
|
||||||
@@ -47,7 +20,6 @@ in {
|
|||||||
wordProcessor = "writer.desktop";
|
wordProcessor = "writer.desktop";
|
||||||
spreadsheet = "calc.desktop";
|
spreadsheet = "calc.desktop";
|
||||||
presentation = "impress.desktop";
|
presentation = "impress.desktop";
|
||||||
|
|
||||||
defaultApplications =
|
defaultApplications =
|
||||||
(mimeMap imageViewer [
|
(mimeMap imageViewer [
|
||||||
"image/avif"
|
"image/avif"
|
||||||
@@ -163,12 +135,82 @@ in {
|
|||||||
"x-scheme-handler/element" = ["element-desktop.desktop"];
|
"x-scheme-handler/element" = ["element-desktop.desktop"];
|
||||||
"x-scheme-handler/magnet" = ["transmission-gtk.desktop"];
|
"x-scheme-handler/magnet" = ["transmission-gtk.desktop"];
|
||||||
};
|
};
|
||||||
|
mimeAppsListText = let
|
||||||
|
formatApplications = applications:
|
||||||
|
lib.concatStringsSep "\n" (
|
||||||
|
lib.mapAttrsToList (
|
||||||
|
mimeType: desktopIds: "${mimeType}=${lib.concatStringsSep ";" desktopIds};"
|
||||||
|
)
|
||||||
|
applications
|
||||||
|
);
|
||||||
|
in ''
|
||||||
|
[Added Associations]
|
||||||
|
${formatApplications defaultApplications}
|
||||||
|
|
||||||
|
[Default Applications]
|
||||||
|
${formatApplications defaultApplications}
|
||||||
|
|
||||||
|
[Removed Associations]
|
||||||
|
'';
|
||||||
in {
|
in {
|
||||||
|
# Automatic garbage collection of old home-manager generations
|
||||||
|
nix.gc = {
|
||||||
|
automatic = true;
|
||||||
|
dates = "weekly";
|
||||||
|
options = "--delete-older-than 7d";
|
||||||
|
};
|
||||||
|
|
||||||
|
xdg.configFile."greenclip.toml".text = ''
|
||||||
|
[greenclip]
|
||||||
|
history_file = "~/.cache/greenclip.history"
|
||||||
|
max_history_length = 50
|
||||||
|
max_selection_size_bytes = 0
|
||||||
|
trim_space_from_selection = true
|
||||||
|
use_primary_selection_as_input = false
|
||||||
|
blacklisted_applications = []
|
||||||
|
enable_image_support = true
|
||||||
|
image_cache_directory = "~/.cache/greenclip"
|
||||||
|
static_history = []
|
||||||
|
'';
|
||||||
|
|
||||||
|
|
||||||
|
xdg.configFile."zellij/config.kdl".source =
|
||||||
|
config.lib.file.mkOutOfStoreSymlink
|
||||||
|
"${config.home.homeDirectory}/dotfiles/dotfiles/config/zellij/config.kdl";
|
||||||
|
|
||||||
|
xdg.configFile."menus/applications.menu" = lib.mkIf nixos.config.myModules.desktop.enable {
|
||||||
|
source = "${pkgs.kdePackages.plasma-workspace}/etc/xdg/menus/plasma-applications.menu";
|
||||||
|
};
|
||||||
|
|
||||||
|
xdg.configFile."kde-mimeapps.list" = lib.mkIf nixos.config.myModules.desktop.enable {
|
||||||
|
text = mimeAppsListText;
|
||||||
|
};
|
||||||
|
|
||||||
|
xdg.configFile."none+xmonad-mimeapps.list" = lib.mkIf nixos.config.myModules.desktop.enable {
|
||||||
|
text = mimeAppsListText;
|
||||||
|
};
|
||||||
|
|
||||||
|
xdg.configFile."xmonad-mimeapps.list" = lib.mkIf nixos.config.myModules.desktop.enable {
|
||||||
|
text = mimeAppsListText;
|
||||||
|
};
|
||||||
|
|
||||||
|
xdg.configFile."hyprland-mimeapps.list" = lib.mkIf nixos.config.myModules.desktop.enable {
|
||||||
|
text = mimeAppsListText;
|
||||||
|
};
|
||||||
|
|
||||||
|
xdg.dataFile."mimeapps.list" = lib.mkIf nixos.config.myModules.desktop.enable {
|
||||||
|
text = mimeAppsListText;
|
||||||
|
};
|
||||||
|
|
||||||
|
xdg.dataFile."applications/kde-mimeapps.list" = lib.mkIf nixos.config.myModules.desktop.enable {
|
||||||
|
text = mimeAppsListText;
|
||||||
|
};
|
||||||
|
|
||||||
|
xdg.mimeApps = lib.mkIf nixos.config.myModules.desktop.enable {
|
||||||
enable = true;
|
enable = true;
|
||||||
associations.added = defaultApplications;
|
associations.added = defaultApplications;
|
||||||
inherit defaultApplications;
|
inherit defaultApplications;
|
||||||
}
|
};
|
||||||
);
|
|
||||||
|
|
||||||
home.activation.refreshChromeDesktopMimeCache = lib.hm.dag.entryAfter ["writeBoundary"] ''
|
home.activation.refreshChromeDesktopMimeCache = lib.hm.dag.entryAfter ["writeBoundary"] ''
|
||||||
applications_dir="$HOME/.local/share/applications"
|
applications_dir="$HOME/.local/share/applications"
|
||||||
@@ -180,6 +222,7 @@ in {
|
|||||||
do
|
do
|
||||||
if [ -f "$desktop_file" ]; then
|
if [ -f "$desktop_file" ]; then
|
||||||
${pkgs.gnused}/bin/sed -i \
|
${pkgs.gnused}/bin/sed -i \
|
||||||
|
-e 's,application/pdf;,,g' \
|
||||||
-e 's,image/gif;,,g' \
|
-e 's,image/gif;,,g' \
|
||||||
-e 's,image/jpeg;,,g' \
|
-e 's,image/jpeg;,,g' \
|
||||||
-e 's,image/png;,,g' \
|
-e 's,image/png;,,g' \
|
||||||
@@ -188,10 +231,34 @@ in {
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
for desktop_file in "$applications_dir"/okular*.desktop "$applications_dir"/vlc*.desktop; do
|
||||||
|
if [ -f "$desktop_file" ]; then
|
||||||
|
${pkgs.gnused}/bin/sed -i \
|
||||||
|
-e 's,image/avif;,,g' \
|
||||||
|
-e 's,image/bmp;,,g' \
|
||||||
|
-e 's,image/gif;,,g' \
|
||||||
|
-e 's,image/heic;,,g' \
|
||||||
|
-e 's,image/heif;,,g' \
|
||||||
|
-e 's,image/jpeg;,,g' \
|
||||||
|
-e 's,image/jxl;,,g' \
|
||||||
|
-e 's,image/png;,,g' \
|
||||||
|
-e 's,image/svg+xml;,,g' \
|
||||||
|
-e 's,image/svg+xml-compressed;,,g' \
|
||||||
|
-e 's,image/tiff;,,g' \
|
||||||
|
-e 's,image/vnd.microsoft.icon;,,g' \
|
||||||
|
-e 's,image/webp;,,g' \
|
||||||
|
"$desktop_file"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
${pkgs.desktop-file-utils}/bin/update-desktop-database "$applications_dir" >/dev/null 2>&1 || true
|
${pkgs.desktop-file-utils}/bin/update-desktop-database "$applications_dir" >/dev/null 2>&1 || true
|
||||||
fi
|
fi
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
home.activation.refreshKdeServiceCache = lib.hm.dag.entryAfter ["refreshChromeDesktopMimeCache"] ''
|
||||||
|
${pkgs.kdePackages.kservice}/bin/kbuildsycoca6 --noincremental >/dev/null 2>&1 || true
|
||||||
|
'';
|
||||||
|
|
||||||
xsession = {
|
xsession = {
|
||||||
enable = true;
|
enable = true;
|
||||||
preferStatusNotifierItems = true;
|
preferStatusNotifierItems = true;
|
||||||
|
|||||||
@@ -1,24 +1,90 @@
|
|||||||
{ config, pkgs, lib, makeEnable, inputs, ... }:
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
makeEnable,
|
||||||
|
inputs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
let
|
let
|
||||||
cfg = config.myModules.hyprland;
|
|
||||||
system = pkgs.stdenv.hostPlatform.system;
|
system = pkgs.stdenv.hostPlatform.system;
|
||||||
enableExternalPluginPackages = !cfg.useLuaConfigBranch;
|
hyprlandInput = inputs.hyprland;
|
||||||
hyprlandInput =
|
hyprlandPluginPackages = [
|
||||||
if cfg.useLuaConfigBranch
|
|
||||||
then inputs.hyprland-lua-config
|
|
||||||
else inputs.hyprland;
|
|
||||||
luaPluginPackages = lib.optionals cfg.useLuaConfigBranch [
|
|
||||||
inputs.hyprNStack.packages.${system}.hyprNStack
|
inputs.hyprNStack.packages.${system}.hyprNStack
|
||||||
inputs.hyprland-plugins-lua.packages.${system}.hyprexpo
|
inputs.hyprland-plugins-lua.packages.${system}.hyprexpo
|
||||||
|
inputs.hyprwinview.packages.${system}.hyprwinview
|
||||||
|
inputs.self.packages.${system}.hypr-workspace-history
|
||||||
];
|
];
|
||||||
hyprexpoPatched = inputs.hyprland-plugins.packages.${system}.hyprexpo.overrideAttrs (old: {
|
hyprRofiWindow = pkgs.writeShellApplication {
|
||||||
patches = (old.patches or [ ]) ++ [
|
name = "hypr_rofi_window";
|
||||||
./patches/hyprexpo-pr-612-workspace-numbers.patch
|
runtimeInputs = [
|
||||||
./patches/hyprexpo-pr-616-bring-mode.patch
|
pkgs.python3
|
||||||
|
pkgs.rofi
|
||||||
|
hyprlandInput.packages.${system}.hyprland
|
||||||
];
|
];
|
||||||
});
|
text = ''
|
||||||
|
exec python3 ${../dotfiles/lib/bin/hypr_rofi_window} "$@"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
hyprShellUi = pkgs.writeShellApplication {
|
||||||
|
name = "hypr_shell_ui";
|
||||||
|
runtimeInputs = [
|
||||||
|
pkgs.rofi
|
||||||
|
hyprRofiWindow
|
||||||
|
inputs.noctalia.packages.${system}.default
|
||||||
|
];
|
||||||
|
text = ''
|
||||||
|
exec ${../dotfiles/lib/bin/hypr_shell_ui} "$@"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
hyprscratchSettings = {
|
||||||
|
daemon_options = "clean";
|
||||||
|
global_options = "";
|
||||||
|
global_rules = "float;size monitor_w*0.95 monitor_h*0.95;center";
|
||||||
|
|
||||||
|
htop = {
|
||||||
|
command = "alacritty --class htop-scratch --title htop -e htop";
|
||||||
|
class = "htop-scratch";
|
||||||
|
};
|
||||||
|
|
||||||
|
volume = {
|
||||||
|
command = "pavucontrol";
|
||||||
|
class = "org.pulseaudio.pavucontrol";
|
||||||
|
};
|
||||||
|
|
||||||
|
spotify = {
|
||||||
|
command = "spotify";
|
||||||
|
class = "spotify";
|
||||||
|
};
|
||||||
|
|
||||||
|
element = {
|
||||||
|
command = "element-desktop";
|
||||||
|
class = "Element";
|
||||||
|
};
|
||||||
|
|
||||||
|
slack = {
|
||||||
|
command = "slack";
|
||||||
|
class = "Slack";
|
||||||
|
};
|
||||||
|
|
||||||
|
transmission = {
|
||||||
|
command = "transmission-gtk";
|
||||||
|
class = "transmission-gtk";
|
||||||
|
};
|
||||||
|
|
||||||
|
dropdown = {
|
||||||
|
command = "ghostty --config-file=/home/imalison/.config/ghostty/dropdown";
|
||||||
|
class = "com.mitchellh.ghostty.dropdown";
|
||||||
|
options = "persist";
|
||||||
|
rules = "float;size monitor_w monitor_h*0.5;move 0 60;noborder;noshadow;animation slide";
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
enabledModule = makeEnable config "myModules.hyprland" true {
|
enabledModule = makeEnable config "myModules.hyprland" true {
|
||||||
myModules.taffybar.enable = 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.
|
# Needed for hyprlock authentication without PAM fallback warnings.
|
||||||
security.pam.services.hyprlock = { };
|
security.pam.services.hyprlock = { };
|
||||||
@@ -39,15 +105,14 @@ let
|
|||||||
|
|
||||||
home-manager.sharedModules = [
|
home-manager.sharedModules = [
|
||||||
inputs.hyprscratch.homeModules.default
|
inputs.hyprscratch.homeModules.default
|
||||||
({ config, ... }: {
|
(
|
||||||
xdg.configFile."hypr" = {
|
{ config, lib, ... }:
|
||||||
force = true;
|
let
|
||||||
source =
|
hyprConfig =
|
||||||
if cfg.useLuaConfigBranch
|
name:
|
||||||
then ../dotfiles/config/hypr
|
config.lib.file.mkOutOfStoreSymlink "${config.home.homeDirectory}/dotfiles/dotfiles/config/hypr/${name}";
|
||||||
else config.lib.file.mkOutOfStoreSymlink "${config.home.homeDirectory}/dotfiles/dotfiles/config/hypr";
|
in
|
||||||
};
|
{
|
||||||
|
|
||||||
services.kanshi = {
|
services.kanshi = {
|
||||||
enable = true;
|
enable = true;
|
||||||
systemdTarget = "graphical-session.target";
|
systemdTarget = "graphical-session.target";
|
||||||
@@ -91,67 +156,34 @@ let
|
|||||||
};
|
};
|
||||||
|
|
||||||
programs.hyprscratch = {
|
programs.hyprscratch = {
|
||||||
enable = !cfg.useLuaConfigBranch;
|
enable = false;
|
||||||
settings = {
|
settings = { };
|
||||||
daemon_options = "clean";
|
|
||||||
global_options = "";
|
|
||||||
global_rules = "float;size monitor_w*0.95 monitor_h*0.95;center";
|
|
||||||
|
|
||||||
htop = {
|
|
||||||
command = "alacritty --class htop-scratch --title htop -e htop";
|
|
||||||
class = "htop-scratch";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
volume = {
|
xdg.configFile."hyprscratch/config.conf" = lib.mkIf false {
|
||||||
command = "pavucontrol";
|
text = lib.hm.generators.toHyprconf {
|
||||||
class = "org.pulseaudio.pavucontrol";
|
attrs = hyprscratchSettings;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
spotify = {
|
xdg.configFile."hypr/hyprland.lua" = {
|
||||||
command = "spotify";
|
force = true;
|
||||||
class = "spotify";
|
source = hyprConfig "hyprland.lua";
|
||||||
};
|
};
|
||||||
|
|
||||||
element = {
|
xdg.configFile."hypr/hypridle.conf".source = hyprConfig "hypridle.conf";
|
||||||
command = "element-desktop";
|
|
||||||
class = "Element";
|
|
||||||
};
|
|
||||||
|
|
||||||
slack = {
|
xdg.configFile."hypr/hyprlock.conf".source = hyprConfig "hyprlock.conf";
|
||||||
command = "slack";
|
|
||||||
class = "Slack";
|
|
||||||
};
|
|
||||||
|
|
||||||
transmission = {
|
xdg.configFile."hypr/scripts".enable = false;
|
||||||
command = "transmission-gtk";
|
}
|
||||||
class = "transmission-gtk";
|
)
|
||||||
};
|
|
||||||
|
|
||||||
dropdown = {
|
|
||||||
command = "ghostty --config-file=/home/imalison/.config/ghostty/dropdown";
|
|
||||||
class = "com.mitchellh.ghostty.dropdown";
|
|
||||||
options = "persist";
|
|
||||||
rules = "float;size monitor_w monitor_h*0.5;move 0 60;noborder;noshadow;animation slide";
|
|
||||||
};
|
|
||||||
|
|
||||||
gmail = {
|
|
||||||
command = "google-chrome-stable --new-window https://mail.google.com/mail/u/0/#inbox";
|
|
||||||
class = "google-chrome";
|
|
||||||
title = "Gmail";
|
|
||||||
};
|
|
||||||
|
|
||||||
messages = {
|
|
||||||
command = "google-chrome-stable --new-window https://messages.google.com/web/conversations";
|
|
||||||
class = "google-chrome";
|
|
||||||
title = "Messages";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
})
|
|
||||||
];
|
];
|
||||||
|
|
||||||
# Hyprland-specific packages
|
# Hyprland-specific packages
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages =
|
||||||
|
with pkgs;
|
||||||
|
[
|
||||||
# Hyprland utilities
|
# Hyprland utilities
|
||||||
hyprpaper # Wallpaper
|
hyprpaper # Wallpaper
|
||||||
hypridle # Idle daemon
|
hypridle # Idle daemon
|
||||||
@@ -164,32 +196,15 @@ let
|
|||||||
slurp # Region selection
|
slurp # Region selection
|
||||||
swappy # Screenshot annotation
|
swappy # Screenshot annotation
|
||||||
nwg-displays # GUI monitor arrangement
|
nwg-displays # GUI monitor arrangement
|
||||||
mpv # Graphical screensaver payload
|
mpvpaper # Layer-shell video screensaver payload
|
||||||
ddcutil # Monitor input switching over DDC/CI
|
ddcutil # Monitor input switching over DDC/CI
|
||||||
|
|
||||||
# For scripts
|
# For scripts
|
||||||
|
hyprRofiWindow
|
||||||
|
hyprShellUi
|
||||||
jq
|
jq
|
||||||
] ++ luaPluginPackages ++ lib.optionals enableExternalPluginPackages [
|
]
|
||||||
# External plugin packages are pinned to the stable 0.53 stack.
|
++ hyprlandPluginPackages;
|
||||||
# Keep hy3 on the stable stack; the Lua branch uses hyprNStack and the
|
|
||||||
# forked Lua-compatible hyprexpo input instead.
|
|
||||||
inputs.hy3.packages.${system}.hy3
|
|
||||||
hyprexpoPatched
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
enabledModule // {
|
enabledModule
|
||||||
options = lib.recursiveUpdate enabledModule.options {
|
|
||||||
myModules.hyprland.useLuaConfigBranch = lib.mkOption {
|
|
||||||
type = lib.types.bool;
|
|
||||||
default = false;
|
|
||||||
description = ''
|
|
||||||
Use the experimental Hyprland PR 13817 Lua-config branch for the
|
|
||||||
Hyprland package itself. The experimental package set excludes hy3, and
|
|
||||||
includes the Lua-branch builds of hyprNStack and hyprexpo instead. When
|
|
||||||
a sibling `hyprland.lua` is present, the Lua config manager picks it
|
|
||||||
before `hyprland.conf`.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -184,16 +184,11 @@
|
|||||||
type = "Application";
|
type = "Application";
|
||||||
categories = [ "Network" "WebBrowser" ];
|
categories = [ "Network" "WebBrowser" ];
|
||||||
mimeType = [
|
mimeType = [
|
||||||
"application/pdf"
|
|
||||||
"application/rdf+xml"
|
"application/rdf+xml"
|
||||||
"application/rss+xml"
|
"application/rss+xml"
|
||||||
"application/xhtml+xml"
|
"application/xhtml+xml"
|
||||||
"application/xhtml_xml"
|
"application/xhtml_xml"
|
||||||
"application/xml"
|
"application/xml"
|
||||||
"image/gif"
|
|
||||||
"image/jpeg"
|
|
||||||
"image/png"
|
|
||||||
"image/webp"
|
|
||||||
"text/html"
|
"text/html"
|
||||||
"text/xml"
|
"text/xml"
|
||||||
"x-scheme-handler/http"
|
"x-scheme-handler/http"
|
||||||
|
|||||||
@@ -63,6 +63,7 @@
|
|||||||
# Disable the old multi-node railbird k3s setup
|
# Disable the old multi-node railbird k3s setup
|
||||||
myModules.railbird-k3s.enable = false;
|
myModules.railbird-k3s.enable = false;
|
||||||
myModules."keepbook-sync".enable = true;
|
myModules."keepbook-sync".enable = true;
|
||||||
|
myModules.remote-hyprland.enable = true;
|
||||||
|
|
||||||
# Mirror the old biskcomp "Syncthing hosting" pattern: serve the synced railbird tree over HTTPS with autoindex.
|
# Mirror the old biskcomp "Syncthing hosting" pattern: serve the synced railbird tree over HTTPS with autoindex.
|
||||||
services.nginx.virtualHosts."syncthing.railbird.ai" = {
|
services.nginx.virtualHosts."syncthing.railbird.ai" = {
|
||||||
|
|||||||
@@ -113,6 +113,9 @@
|
|||||||
codex = inputs.codex-cli-nix.packages.${prev.stdenv.hostPlatform.system}.default;
|
codex = inputs.codex-cli-nix.packages.${prev.stdenv.hostPlatform.system}.default;
|
||||||
claude-code = inputs.claude-code-nix.packages.${prev.stdenv.hostPlatform.system}.default;
|
claude-code = inputs.claude-code-nix.packages.${prev.stdenv.hostPlatform.system}.default;
|
||||||
git-sync-rs = inputs.git-sync-rs.packages.${prev.stdenv.hostPlatform.system}.default;
|
git-sync-rs = inputs.git-sync-rs.packages.${prev.stdenv.hostPlatform.system}.default;
|
||||||
|
kef = final.callPackage ./packages/kef {};
|
||||||
|
pykefcontrol = final.python3Packages.callPackage ./packages/pykefcontrol {};
|
||||||
|
roborock-control = final.callPackage ./packages/roborock-control {};
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
++ (
|
++ (
|
||||||
|
|||||||
87
nixos/noctalia.nix
Normal file
87
nixos/noctalia.nix
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
inputs,
|
||||||
|
lib,
|
||||||
|
makeEnable,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
system = pkgs.stdenv.hostPlatform.system;
|
||||||
|
noctaliaPackage = inputs.noctalia.packages.${system}.default;
|
||||||
|
waitForWayland = pkgs.writeShellScript "noctalia-wait-for-wayland" ''
|
||||||
|
runtime_dir="''${XDG_RUNTIME_DIR:-/run/user/$(${pkgs.coreutils}/bin/id -u)}"
|
||||||
|
|
||||||
|
for _ in $(${pkgs.coreutils}/bin/seq 1 50); do
|
||||||
|
if [ -n "''${WAYLAND_DISPLAY:-}" ] && [ -S "$runtime_dir/$WAYLAND_DISPLAY" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
${pkgs.coreutils}/bin/sleep 0.1
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Wayland socket not ready: WAYLAND_DISPLAY=''${WAYLAND_DISPLAY:-<unset>} XDG_RUNTIME_DIR=$runtime_dir" >&2
|
||||||
|
exit 1
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
makeEnable config "myModules.noctalia" false {
|
||||||
|
environment.systemPackages = [
|
||||||
|
noctaliaPackage
|
||||||
|
];
|
||||||
|
|
||||||
|
# Noctalia's battery widget talks to UPower. Hosts that deliberately do not
|
||||||
|
# have batteries can still override this back to false.
|
||||||
|
services.upower.enable = lib.mkDefault true;
|
||||||
|
|
||||||
|
home-manager.sharedModules = [
|
||||||
|
inputs.noctalia.homeModules.default
|
||||||
|
({ lib, ... }: {
|
||||||
|
programs.noctalia-shell = {
|
||||||
|
enable = true;
|
||||||
|
# This module provides the Hyprland-scoped service below.
|
||||||
|
systemd.enable = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
home.activation.noctaliaLauncherOverviewLayer =
|
||||||
|
lib.hm.dag.entryAfter [ "writeBoundary" ] ''
|
||||||
|
settings_file="$HOME/.config/noctalia/settings.json"
|
||||||
|
settings_tmp="$(${pkgs.coreutils}/bin/mktemp)"
|
||||||
|
|
||||||
|
${pkgs.coreutils}/bin/mkdir -p "$(${pkgs.coreutils}/bin/dirname "$settings_file")"
|
||||||
|
|
||||||
|
if [ -e "$settings_file" ] && ${lib.getExe pkgs.jq} -e . "$settings_file" >/dev/null 2>&1; then
|
||||||
|
${lib.getExe pkgs.jq} \
|
||||||
|
'.appLauncher = (.appLauncher // {}) | .appLauncher.overviewLayer = true' \
|
||||||
|
"$settings_file" > "$settings_tmp"
|
||||||
|
${pkgs.coreutils}/bin/mv "$settings_tmp" "$settings_file"
|
||||||
|
else
|
||||||
|
${pkgs.coreutils}/bin/printf '%s\n' \
|
||||||
|
'{' \
|
||||||
|
' "appLauncher": {' \
|
||||||
|
' "overviewLayer": true' \
|
||||||
|
' }' \
|
||||||
|
'}' > "$settings_file"
|
||||||
|
${pkgs.coreutils}/bin/rm -f "$settings_tmp"
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
|
||||||
|
systemd.user.services.noctalia-shell = {
|
||||||
|
Unit = {
|
||||||
|
Description = "Noctalia Shell";
|
||||||
|
Documentation = "https://docs.noctalia.dev";
|
||||||
|
PartOf = [ "hyprland-session.target" ];
|
||||||
|
After = [ "hyprland-session.target" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
Service = {
|
||||||
|
ExecCondition = "/run/current-system/sw/bin/desktop_shell_ui exec-condition noctalia";
|
||||||
|
ExecStartPre = "${waitForWayland}";
|
||||||
|
ExecStart = "${lib.getExe noctaliaPackage} --no-duplicate";
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
Install.WantedBy = [ "hyprland-session.target" ];
|
||||||
|
};
|
||||||
|
})
|
||||||
|
];
|
||||||
|
}
|
||||||
30
nixos/packages/kef/default.nix
Normal file
30
nixos/packages/kef/default.nix
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
python3,
|
||||||
|
python3Packages,
|
||||||
|
writeShellApplication,
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
pykefcontrol = python3Packages.callPackage ../pykefcontrol {};
|
||||||
|
python = python3.withPackages (ps: [
|
||||||
|
pykefcontrol
|
||||||
|
ps.zeroconf
|
||||||
|
]);
|
||||||
|
in
|
||||||
|
writeShellApplication {
|
||||||
|
name = "kef";
|
||||||
|
|
||||||
|
runtimeInputs = [ python ];
|
||||||
|
|
||||||
|
text = ''
|
||||||
|
exec python ${./kef.py} "$@"
|
||||||
|
'';
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
description = "Command-line controller for KEF W2 speakers";
|
||||||
|
license = lib.licenses.mit;
|
||||||
|
maintainers = with lib.maintainers; [ imalison ];
|
||||||
|
mainProgram = "kef";
|
||||||
|
};
|
||||||
|
}
|
||||||
251
nixos/packages/kef/kef.py
Normal file
251
nixos/packages/kef/kef.py
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from pykefcontrol.kef_connector import KefConnector
|
||||||
|
from zeroconf import IPVersion, ServiceBrowser, ServiceListener, Zeroconf
|
||||||
|
|
||||||
|
|
||||||
|
SOURCES = ("wifi", "bluetooth", "tv", "optic", "coaxial", "analog", "standby")
|
||||||
|
DISCOVERY_SERVICE_TYPES = (
|
||||||
|
"_kef-info._tcp.local.",
|
||||||
|
"_sues800device._tcp.local.",
|
||||||
|
"_http._tcp.local.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class KefDiscoveryListener(ServiceListener):
|
||||||
|
def __init__(self):
|
||||||
|
self._lock = threading.Lock()
|
||||||
|
self.speakers = {}
|
||||||
|
|
||||||
|
def add_service(self, zeroconf, service_type, name):
|
||||||
|
self._record_service(zeroconf, service_type, name)
|
||||||
|
|
||||||
|
def update_service(self, zeroconf, service_type, name):
|
||||||
|
self._record_service(zeroconf, service_type, name)
|
||||||
|
|
||||||
|
def remove_service(self, zeroconf, service_type, name):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _record_service(self, zeroconf, service_type, name):
|
||||||
|
info = zeroconf.get_service_info(service_type, name, timeout=1000)
|
||||||
|
if info is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
properties = {
|
||||||
|
key.decode("utf-8", errors="replace"): (
|
||||||
|
value.decode("utf-8", errors="replace") if value is not None else ""
|
||||||
|
)
|
||||||
|
for key, value in info.properties.items()
|
||||||
|
}
|
||||||
|
addresses = [
|
||||||
|
address
|
||||||
|
for address in info.parsed_addresses(IPVersion.V4Only)
|
||||||
|
if not address.startswith("127.")
|
||||||
|
]
|
||||||
|
if not addresses:
|
||||||
|
return
|
||||||
|
|
||||||
|
is_kef = (
|
||||||
|
service_type in ("_kef-info._tcp.local.", "_sues800device._tcp.local.")
|
||||||
|
or properties.get("manufacturer", "").lower() == "kef"
|
||||||
|
or "kef" in name.lower()
|
||||||
|
or "ls50" in name.lower()
|
||||||
|
)
|
||||||
|
if not is_kef:
|
||||||
|
return
|
||||||
|
|
||||||
|
key = addresses[0]
|
||||||
|
with self._lock:
|
||||||
|
existing = self.speakers.get(key, {})
|
||||||
|
self.speakers[key] = {
|
||||||
|
**existing,
|
||||||
|
"address": addresses[0],
|
||||||
|
"hostname": (info.server.rstrip(".") if info.server else None)
|
||||||
|
or existing.get("hostname"),
|
||||||
|
"name": properties.get("name")
|
||||||
|
or properties.get("fn")
|
||||||
|
or existing.get("name")
|
||||||
|
or strip_service_name(name),
|
||||||
|
"model": properties.get("modelName")
|
||||||
|
or properties.get("model")
|
||||||
|
or properties.get("mn")
|
||||||
|
or existing.get("model"),
|
||||||
|
"version": properties.get("version") or existing.get("version"),
|
||||||
|
"service": existing.get("service") or service_type.removesuffix(".local."),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def strip_service_name(name):
|
||||||
|
for suffix in DISCOVERY_SERVICE_TYPES:
|
||||||
|
if name.endswith("." + suffix):
|
||||||
|
return name[: -(len(suffix) + 1)]
|
||||||
|
return name.rstrip(".")
|
||||||
|
|
||||||
|
|
||||||
|
def discover_speakers(timeout):
|
||||||
|
listener = KefDiscoveryListener()
|
||||||
|
zeroconf = Zeroconf(ip_version=IPVersion.V4Only)
|
||||||
|
try:
|
||||||
|
browsers = [
|
||||||
|
ServiceBrowser(zeroconf, service_type, listener)
|
||||||
|
for service_type in DISCOVERY_SERVICE_TYPES
|
||||||
|
]
|
||||||
|
deadline = time.monotonic() + timeout
|
||||||
|
while time.monotonic() < deadline:
|
||||||
|
if listener.speakers:
|
||||||
|
time.sleep(0.25)
|
||||||
|
break
|
||||||
|
time.sleep(0.05)
|
||||||
|
return sorted(
|
||||||
|
listener.speakers.values(),
|
||||||
|
key=lambda speaker: (
|
||||||
|
speaker.get("name") or "",
|
||||||
|
speaker.get("address") or "",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
for browser in locals().get("browsers", []):
|
||||||
|
browser.cancel()
|
||||||
|
zeroconf.close()
|
||||||
|
|
||||||
|
|
||||||
|
def discover_host(timeout):
|
||||||
|
speakers = discover_speakers(timeout)
|
||||||
|
if not speakers:
|
||||||
|
raise SystemExit(
|
||||||
|
"Could not discover a KEF speaker. Pass --host <speaker-ip> or set KEF_HOST."
|
||||||
|
)
|
||||||
|
if len(speakers) > 1:
|
||||||
|
choices = ", ".join(
|
||||||
|
f"{speaker.get('name') or 'KEF'} at {speaker['address']}" for speaker in speakers
|
||||||
|
)
|
||||||
|
raise SystemExit(f"Multiple KEF speakers discovered: {choices}. Pass --host explicitly.")
|
||||||
|
return speakers[0]["address"]
|
||||||
|
|
||||||
|
|
||||||
|
def connector(args):
|
||||||
|
host = (
|
||||||
|
args.host
|
||||||
|
or os.environ.get("KEF_HOST")
|
||||||
|
or os.environ.get("KEF_IP")
|
||||||
|
or discover_host(args.discovery_timeout)
|
||||||
|
)
|
||||||
|
return KefConnector(host, model=args.model)
|
||||||
|
|
||||||
|
|
||||||
|
def print_status(speaker):
|
||||||
|
info = {
|
||||||
|
"name": speaker.speaker_name,
|
||||||
|
"model": speaker.speaker_model,
|
||||||
|
"firmware": speaker.firmware_version,
|
||||||
|
"status": speaker.status,
|
||||||
|
"source": speaker.source,
|
||||||
|
"volume": speaker.volume,
|
||||||
|
"playing": speaker.is_playing,
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
song = speaker.get_song_information()
|
||||||
|
if any(song.values()):
|
||||||
|
info["song"] = song
|
||||||
|
except (KeyError, IndexError, TypeError, requests.RequestException):
|
||||||
|
pass
|
||||||
|
print(json.dumps(info, indent=2, sort_keys=True))
|
||||||
|
|
||||||
|
|
||||||
|
def bounded_volume(value):
|
||||||
|
return max(0, min(100, value))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(prog="kef", description="Control KEF W2 speakers.")
|
||||||
|
parser.add_argument(
|
||||||
|
"--host",
|
||||||
|
help="Speaker IP address. Defaults to KEF_HOST/KEF_IP, then mDNS discovery.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--model",
|
||||||
|
default="LS50W2",
|
||||||
|
help="Speaker model passed to pykefcontrol. Default: LS50W2.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--discovery-timeout",
|
||||||
|
default=2.0,
|
||||||
|
type=float,
|
||||||
|
help="Seconds to wait for mDNS discovery when --host/KEF_HOST is unset.",
|
||||||
|
)
|
||||||
|
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||||
|
|
||||||
|
subparsers.add_parser("discover", help="List discovered KEF speakers as JSON.")
|
||||||
|
subparsers.add_parser("status", help="Print speaker status as JSON.")
|
||||||
|
subparsers.add_parser("on", help="Power on.")
|
||||||
|
subparsers.add_parser("standby", help="Put the speaker in standby.")
|
||||||
|
subparsers.add_parser("mute", help="Mute by setting volume to 0.")
|
||||||
|
|
||||||
|
volume = subparsers.add_parser("volume", help="Get or set volume.")
|
||||||
|
volume.add_argument("level", nargs="?", type=int, help="Volume level, 0-100.")
|
||||||
|
|
||||||
|
up = subparsers.add_parser("up", help="Increase volume.")
|
||||||
|
up.add_argument("step", nargs="?", default=3, type=int)
|
||||||
|
|
||||||
|
down = subparsers.add_parser("down", help="Decrease volume.")
|
||||||
|
down.add_argument("step", nargs="?", default=3, type=int)
|
||||||
|
|
||||||
|
source = subparsers.add_parser("source", help="Get or set source.")
|
||||||
|
source.add_argument("name", nargs="?", choices=SOURCES)
|
||||||
|
|
||||||
|
subparsers.add_parser("play-pause", help="Toggle play/pause.")
|
||||||
|
subparsers.add_parser("next", help="Next track.")
|
||||||
|
subparsers.add_parser("previous", help="Previous track.")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
if args.command == "discover":
|
||||||
|
print(json.dumps(discover_speakers(args.discovery_timeout), indent=2, sort_keys=True))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
speaker = connector(args)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if args.command == "status":
|
||||||
|
print_status(speaker)
|
||||||
|
elif args.command == "on":
|
||||||
|
speaker.power_on()
|
||||||
|
elif args.command == "standby":
|
||||||
|
speaker.shutdown()
|
||||||
|
elif args.command == "mute":
|
||||||
|
speaker.volume = 0
|
||||||
|
elif args.command == "volume":
|
||||||
|
if args.level is None:
|
||||||
|
print(speaker.volume)
|
||||||
|
else:
|
||||||
|
speaker.volume = bounded_volume(args.level)
|
||||||
|
elif args.command == "up":
|
||||||
|
speaker.volume = bounded_volume(speaker.volume + args.step)
|
||||||
|
elif args.command == "down":
|
||||||
|
speaker.volume = bounded_volume(speaker.volume - args.step)
|
||||||
|
elif args.command == "source":
|
||||||
|
if args.name is None:
|
||||||
|
print(speaker.source)
|
||||||
|
else:
|
||||||
|
speaker.source = args.name
|
||||||
|
elif args.command == "play-pause":
|
||||||
|
speaker.toggle_play_pause()
|
||||||
|
elif args.command == "next":
|
||||||
|
speaker.next_track()
|
||||||
|
elif args.command == "previous":
|
||||||
|
speaker.previous_track()
|
||||||
|
except requests.RequestException as error:
|
||||||
|
print(f"kef: failed to reach speaker: {error}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
37
nixos/packages/pykefcontrol/default.nix
Normal file
37
nixos/packages/pykefcontrol/default.nix
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
buildPythonPackage,
|
||||||
|
fetchPypi,
|
||||||
|
hatchling,
|
||||||
|
aiohttp,
|
||||||
|
requests,
|
||||||
|
}:
|
||||||
|
|
||||||
|
buildPythonPackage rec {
|
||||||
|
pname = "pykefcontrol";
|
||||||
|
version = "0.9.2";
|
||||||
|
pyproject = true;
|
||||||
|
|
||||||
|
src = fetchPypi {
|
||||||
|
inherit pname version;
|
||||||
|
hash = "sha256-3kGhN+E7driiE6ePyF0EZOEnUhTm07sxHCKdzrn/MxM=";
|
||||||
|
};
|
||||||
|
|
||||||
|
build-system = [
|
||||||
|
hatchling
|
||||||
|
];
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
aiohttp
|
||||||
|
requests
|
||||||
|
];
|
||||||
|
|
||||||
|
pythonImportsCheck = [ "pykefcontrol" ];
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
description = "Python library for controlling KEF LS50WII, LSX II, and LS60 speakers";
|
||||||
|
homepage = "https://github.com/N0ciple/pykefcontrol";
|
||||||
|
license = lib.licenses.mit;
|
||||||
|
maintainers = with lib.maintainers; [ imalison ];
|
||||||
|
};
|
||||||
|
}
|
||||||
30
nixos/packages/roborock-control/default.nix
Normal file
30
nixos/packages/roborock-control/default.nix
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
python3,
|
||||||
|
writeShellApplication,
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
python = python3.withPackages (ps: [
|
||||||
|
ps.python-roborock
|
||||||
|
ps.pyshark
|
||||||
|
ps.pyyaml
|
||||||
|
]);
|
||||||
|
in
|
||||||
|
writeShellApplication {
|
||||||
|
name = "roborock-control";
|
||||||
|
|
||||||
|
runtimeInputs = [ python ];
|
||||||
|
|
||||||
|
text = ''
|
||||||
|
export ROBOROCK_CONTROL_RUNNER=direct
|
||||||
|
exec python ${../../../dotfiles/lib/bin/roborock-control} "$@"
|
||||||
|
'';
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
description = "Command-line controller for Roborock vacuums";
|
||||||
|
license = lib.licenses.mit;
|
||||||
|
maintainers = with lib.maintainers; [ imalison ];
|
||||||
|
mainProgram = "roborock-control";
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,228 +0,0 @@
|
|||||||
From aaefc0ff0bc4348de04f311ad0101da44c62ae94 Mon Sep 17 00:00:00 2001
|
|
||||||
From: Ivan Malison <IvanMalison@gmail.com>
|
|
||||||
Date: Wed, 4 Feb 2026 00:54:52 -0800
|
|
||||||
Subject: [PATCH 1/2] hyprexpo: optionally render workspace numbers
|
|
||||||
|
|
||||||
---
|
|
||||||
hyprexpo/README.md | 3 +-
|
|
||||||
hyprexpo/main.cpp | 2 +
|
|
||||||
hyprexpo/overview.cpp | 109 ++++++++++++++++++++++++++++++++++++++++++
|
|
||||||
hyprexpo/overview.hpp | 4 ++
|
|
||||||
4 files changed, 117 insertions(+), 1 deletion(-)
|
|
||||||
|
|
||||||
diff --git a/README.md b/README.md
|
|
||||||
index 97bd1d4..aac2e97 100644
|
|
||||||
--- a/README.md
|
|
||||||
+++ b/README.md
|
|
||||||
@@ -28,6 +28,8 @@ gap_size | number | gap between desktops | `5`
|
|
||||||
bg_col | color | color in gaps (between desktops) | `rgb(000000)`
|
|
||||||
workspace_method | [center/first] [workspace] | position of the desktops | `center current`
|
|
||||||
skip_empty | boolean | whether the grid displays workspaces sequentially by id using selector "r" (`false`) or skips empty workspaces using selector "m" (`true`) | `false`
|
|
||||||
+show_workspace_numbers | boolean | show numeric labels for workspaces | `false`
|
|
||||||
+workspace_number_color | color | color of workspace number labels | `rgb(ffffff)`
|
|
||||||
gesture_distance | number | how far is the max for the gesture | `300`
|
|
||||||
|
|
||||||
### Keywords
|
|
||||||
@@ -57,4 +59,3 @@ off | hides the overview
|
|
||||||
disable | same as `off`
|
|
||||||
on | displays the overview
|
|
||||||
enable | same as `on`
|
|
||||||
-
|
|
||||||
diff --git a/main.cpp b/main.cpp
|
|
||||||
index 883fd82..ff9f380 100644
|
|
||||||
--- a/main.cpp
|
|
||||||
+++ b/main.cpp
|
|
||||||
@@ -239,6 +239,8 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
|
|
||||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:bg_col", Hyprlang::INT{0xFF111111});
|
|
||||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:workspace_method", Hyprlang::STRING{"center current"});
|
|
||||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:skip_empty", Hyprlang::INT{0});
|
|
||||||
+ HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:show_workspace_numbers", Hyprlang::INT{0});
|
|
||||||
+ HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:workspace_number_color", Hyprlang::INT{0xFFFFFFFF});
|
|
||||||
|
|
||||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:gesture_distance", Hyprlang::INT{200});
|
|
||||||
|
|
||||||
diff --git a/overview.cpp b/overview.cpp
|
|
||||||
index 5721948..926a9f8 100644
|
|
||||||
--- a/overview.cpp
|
|
||||||
+++ b/overview.cpp
|
|
||||||
@@ -1,5 +1,8 @@
|
|
||||||
#include "overview.hpp"
|
|
||||||
#include <any>
|
|
||||||
+#include <algorithm>
|
|
||||||
+#include <cmath>
|
|
||||||
+#include <pango/pangocairo.h>
|
|
||||||
#define private public
|
|
||||||
#include <hyprland/src/render/Renderer.hpp>
|
|
||||||
#include <hyprland/src/Compositor.hpp>
|
|
||||||
@@ -15,6 +18,86 @@
|
|
||||||
#undef private
|
|
||||||
#include "OverviewPassElement.hpp"
|
|
||||||
|
|
||||||
+static Vector2D renderLabelTexture(SP<CTexture> out, const std::string& text, const CHyprColor& color, int fontSizePx) {
|
|
||||||
+ if (!out || text.empty() || fontSizePx <= 0)
|
|
||||||
+ return {};
|
|
||||||
+
|
|
||||||
+ auto measureSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1);
|
|
||||||
+ auto measureCairo = cairo_create(measureSurface);
|
|
||||||
+
|
|
||||||
+ PangoLayout* measureLayout = pango_cairo_create_layout(measureCairo);
|
|
||||||
+ pango_layout_set_text(measureLayout, text.c_str(), -1);
|
|
||||||
+ auto* fontDesc = pango_font_description_from_string("Sans Bold");
|
|
||||||
+ pango_font_description_set_size(fontDesc, fontSizePx * PANGO_SCALE);
|
|
||||||
+ pango_layout_set_font_description(measureLayout, fontDesc);
|
|
||||||
+ pango_font_description_free(fontDesc);
|
|
||||||
+
|
|
||||||
+ PangoRectangle inkRect, logicalRect;
|
|
||||||
+ pango_layout_get_extents(measureLayout, &inkRect, &logicalRect);
|
|
||||||
+
|
|
||||||
+ const int textW = std::max(1, (int)std::ceil(logicalRect.width / (double)PANGO_SCALE));
|
|
||||||
+ const int textH = std::max(1, (int)std::ceil(logicalRect.height / (double)PANGO_SCALE));
|
|
||||||
+
|
|
||||||
+ g_object_unref(measureLayout);
|
|
||||||
+ cairo_destroy(measureCairo);
|
|
||||||
+ cairo_surface_destroy(measureSurface);
|
|
||||||
+
|
|
||||||
+ const int pad = std::max(4, (int)std::round(fontSizePx * 0.35));
|
|
||||||
+ const int width = textW + pad * 2;
|
|
||||||
+ const int height = textH + pad * 2;
|
|
||||||
+
|
|
||||||
+ auto surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
|
|
||||||
+ auto cairo = cairo_create(surface);
|
|
||||||
+
|
|
||||||
+ // Clear the pixmap
|
|
||||||
+ cairo_save(cairo);
|
|
||||||
+ cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR);
|
|
||||||
+ cairo_paint(cairo);
|
|
||||||
+ cairo_restore(cairo);
|
|
||||||
+
|
|
||||||
+ // Background for legibility
|
|
||||||
+ cairo_set_source_rgba(cairo, 0.0, 0.0, 0.0, 0.55);
|
|
||||||
+ cairo_rectangle(cairo, 0, 0, width, height);
|
|
||||||
+ cairo_fill(cairo);
|
|
||||||
+
|
|
||||||
+ PangoLayout* layout = pango_cairo_create_layout(cairo);
|
|
||||||
+ pango_layout_set_text(layout, text.c_str(), -1);
|
|
||||||
+ fontDesc = pango_font_description_from_string("Sans Bold");
|
|
||||||
+ pango_font_description_set_size(fontDesc, fontSizePx * PANGO_SCALE);
|
|
||||||
+ pango_layout_set_font_description(layout, fontDesc);
|
|
||||||
+ pango_font_description_free(fontDesc);
|
|
||||||
+
|
|
||||||
+ pango_layout_get_extents(layout, &inkRect, &logicalRect);
|
|
||||||
+ const double xOffset = (width - logicalRect.width / (double)PANGO_SCALE) / 2.0;
|
|
||||||
+ const double yOffset = (height - logicalRect.height / (double)PANGO_SCALE) / 2.0;
|
|
||||||
+
|
|
||||||
+ cairo_set_source_rgba(cairo, color.r, color.g, color.b, color.a);
|
|
||||||
+ cairo_move_to(cairo, xOffset, yOffset);
|
|
||||||
+ pango_cairo_show_layout(cairo, layout);
|
|
||||||
+
|
|
||||||
+ g_object_unref(layout);
|
|
||||||
+
|
|
||||||
+ cairo_surface_flush(surface);
|
|
||||||
+
|
|
||||||
+ const auto DATA = cairo_image_surface_get_data(surface);
|
|
||||||
+ out->allocate();
|
|
||||||
+ glBindTexture(GL_TEXTURE_2D, out->m_texID);
|
|
||||||
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
||||||
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
||||||
+
|
|
||||||
+#ifndef GLES2
|
|
||||||
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE);
|
|
||||||
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
|
|
||||||
+#endif
|
|
||||||
+
|
|
||||||
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA);
|
|
||||||
+
|
|
||||||
+ cairo_destroy(cairo);
|
|
||||||
+ cairo_surface_destroy(surface);
|
|
||||||
+
|
|
||||||
+ return {width, height};
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
static void damageMonitor(WP<Hyprutils::Animation::CBaseAnimatedVariable> thisptr) {
|
|
||||||
g_pOverview->damage();
|
|
||||||
}
|
|
||||||
@@ -34,11 +117,14 @@ COverview::COverview(PHLWORKSPACE startedOn_, bool swipe_) : startedOn(startedOn
|
|
||||||
static auto* const* PGAPS = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:gap_size")->getDataStaticPtr();
|
|
||||||
static auto* const* PCOL = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:bg_col")->getDataStaticPtr();
|
|
||||||
static auto* const* PSKIP = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:skip_empty")->getDataStaticPtr();
|
|
||||||
+ static auto* const* PSHOWNUM = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:show_workspace_numbers")->getDataStaticPtr();
|
|
||||||
+ static auto* const* PNUMCOL = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:workspace_number_color")->getDataStaticPtr();
|
|
||||||
static auto const* PMETHOD = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:workspace_method")->getDataStaticPtr();
|
|
||||||
|
|
||||||
SIDE_LENGTH = **PCOLUMNS;
|
|
||||||
GAP_WIDTH = **PGAPS;
|
|
||||||
BG_COLOR = **PCOL;
|
|
||||||
+ showWorkspaceNumbers = **PSHOWNUM;
|
|
||||||
|
|
||||||
// process the method
|
|
||||||
bool methodCenter = true;
|
|
||||||
@@ -126,6 +212,17 @@ COverview::COverview(PHLWORKSPACE startedOn_, bool swipe_) : startedOn(startedOn
|
|
||||||
Vector2D tileRenderSize = (pMonitor->m_size - Vector2D{GAP_WIDTH * pMonitor->m_scale, GAP_WIDTH * pMonitor->m_scale} * (SIDE_LENGTH - 1)) / SIDE_LENGTH;
|
|
||||||
CBox monbox{0, 0, tileSize.x * 2, tileSize.y * 2};
|
|
||||||
|
|
||||||
+ if (showWorkspaceNumbers) {
|
|
||||||
+ const CHyprColor numberColor = **PNUMCOL;
|
|
||||||
+ const int fontSizePx = std::max(12, (int)std::round(tileRenderSize.y * pMonitor->m_scale * 0.22));
|
|
||||||
+ for (auto& image : images) {
|
|
||||||
+ if (image.workspaceID == WORKSPACE_INVALID)
|
|
||||||
+ continue;
|
|
||||||
+ image.labelTex = makeShared<CTexture>();
|
|
||||||
+ image.labelSizePx = renderLabelTexture(image.labelTex, std::to_string(image.workspaceID), numberColor, fontSizePx);
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
if (!ENABLE_LOWRES)
|
|
||||||
monbox = {{0, 0}, pMonitor->m_pixelSize};
|
|
||||||
|
|
||||||
@@ -452,6 +549,18 @@ void COverview::fullRender() {
|
|
||||||
texbox.round();
|
|
||||||
CRegion damage{0, 0, INT16_MAX, INT16_MAX};
|
|
||||||
g_pHyprOpenGL->renderTextureInternal(images[x + y * SIDE_LENGTH].fb.getTexture(), texbox, {.damage = &damage, .a = 1.0});
|
|
||||||
+
|
|
||||||
+ if (showWorkspaceNumbers) {
|
|
||||||
+ auto& image = images[x + y * SIDE_LENGTH];
|
|
||||||
+ if (image.workspaceID != WORKSPACE_INVALID && image.labelTex && image.labelTex->m_texID != 0 && image.labelSizePx.x > 0 && image.labelSizePx.y > 0) {
|
|
||||||
+ const Vector2D labelSize = image.labelSizePx / pMonitor->m_scale;
|
|
||||||
+ const float margin = std::max(4.0, tileRenderSize.y * 0.05);
|
|
||||||
+ CBox labelBox = {x * tileRenderSize.x + x * GAPSIZE + margin, y * tileRenderSize.y + y * GAPSIZE + margin, labelSize.x, labelSize.y};
|
|
||||||
+ labelBox.scale(pMonitor->m_scale).translate(pos->value());
|
|
||||||
+ labelBox.round();
|
|
||||||
+ g_pHyprOpenGL->renderTexture(image.labelTex, labelBox, {.a = 1.0});
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
diff --git a/overview.hpp b/overview.hpp
|
|
||||||
index 4b02400..1f6bf3c 100644
|
|
||||||
--- a/overview.hpp
|
|
||||||
+++ b/overview.hpp
|
|
||||||
@@ -8,6 +8,7 @@
|
|
||||||
#include <hyprland/src/helpers/AnimatedVariable.hpp>
|
|
||||||
#include <hyprland/src/managers/HookSystemManager.hpp>
|
|
||||||
#include <vector>
|
|
||||||
+class CTexture;
|
|
||||||
|
|
||||||
// saves on resources, but is a bit broken rn with blur.
|
|
||||||
// hyprland's fault, but cba to fix.
|
|
||||||
@@ -58,6 +59,8 @@ class COverview {
|
|
||||||
int64_t workspaceID = -1;
|
|
||||||
PHLWORKSPACE pWorkspace;
|
|
||||||
CBox box;
|
|
||||||
+ SP<CTexture> labelTex;
|
|
||||||
+ Vector2D labelSizePx;
|
|
||||||
};
|
|
||||||
|
|
||||||
Vector2D lastMousePosLocal = Vector2D{};
|
|
||||||
@@ -81,6 +84,7 @@ class COverview {
|
|
||||||
|
|
||||||
bool swipe = false;
|
|
||||||
bool swipeWasCommenced = false;
|
|
||||||
+ bool showWorkspaceNumbers = false;
|
|
||||||
|
|
||||||
friend class COverviewPassElement;
|
|
||||||
};
|
|
||||||
--
|
|
||||||
2.52.0
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
From edc05ce88f79ceda0cdcb9aa68ec371b1af323de Mon Sep 17 00:00:00 2001
|
|
||||||
From: Ivan Malison <IvanMalison@gmail.com>
|
|
||||||
Date: Mon, 16 Feb 2026 21:50:16 -0800
|
|
||||||
Subject: [PATCH 2/2] hyprexpo: add bring selection mode
|
|
||||||
|
|
||||||
---
|
|
||||||
hyprexpo/README.md | 1 +
|
|
||||||
hyprexpo/main.cpp | 50 +++++++++++++++++++++++++++++++++++++++++++
|
|
||||||
hyprexpo/overview.cpp | 12 +++++++++--
|
|
||||||
hyprexpo/overview.hpp | 3 ++-
|
|
||||||
4 files changed, 63 insertions(+), 3 deletions(-)
|
|
||||||
|
|
||||||
diff --git a/README.md b/README.md
|
|
||||||
index aac2e97..084f02b 100644
|
|
||||||
--- a/README.md
|
|
||||||
+++ b/README.md
|
|
||||||
@@ -55,6 +55,7 @@ Here are a list of options you can use:
|
|
||||||
| --- | --- |
|
|
||||||
toggle | displays if hidden, hide if displayed
|
|
||||||
select | selects the hovered desktop
|
|
||||||
+bring | brings a window from the hovered desktop to the current desktop
|
|
||||||
off | hides the overview
|
|
||||||
disable | same as `off`
|
|
||||||
on | displays the overview
|
|
||||||
diff --git a/main.cpp b/main.cpp
|
|
||||||
index ff9f380..78bac24 100644
|
|
||||||
--- a/main.cpp
|
|
||||||
+++ b/main.cpp
|
|
||||||
@@ -65,6 +65,47 @@ static void hkAddDamageB(void* thisptr, const pixman_region32_t* rg) {
|
|
||||||
g_pOverview->onDamageReported();
|
|
||||||
}
|
|
||||||
|
|
||||||
+static PHLWINDOW windowToBringFromWorkspace(const PHLWORKSPACE& workspace) {
|
|
||||||
+ if (!workspace)
|
|
||||||
+ return nullptr;
|
|
||||||
+
|
|
||||||
+ for (auto it = g_pCompositor->m_windows.rbegin(); it != g_pCompositor->m_windows.rend(); ++it) {
|
|
||||||
+ const auto& w = *it;
|
|
||||||
+ if (!w || w->m_workspace != workspace || !w->m_isMapped || w->isHidden())
|
|
||||||
+ continue;
|
|
||||||
+
|
|
||||||
+ return w;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ return nullptr;
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+static SDispatchResult bringWindowFromWorkspace(int64_t sourceWorkspaceID) {
|
|
||||||
+ if (sourceWorkspaceID == WORKSPACE_INVALID)
|
|
||||||
+ return {.success = false, .error = "selected workspace is empty"};
|
|
||||||
+
|
|
||||||
+ const auto FOCUSSTATE = Desktop::focusState();
|
|
||||||
+ const auto MONITOR = FOCUSSTATE->monitor();
|
|
||||||
+ if (!MONITOR || !MONITOR->m_activeWorkspace)
|
|
||||||
+ return {.success = false, .error = "no active monitor/workspace"};
|
|
||||||
+
|
|
||||||
+ if (sourceWorkspaceID == MONITOR->activeWorkspaceID())
|
|
||||||
+ return {};
|
|
||||||
+
|
|
||||||
+ const auto SOURCEWORKSPACE = g_pCompositor->getWorkspaceByID(sourceWorkspaceID);
|
|
||||||
+ if (!SOURCEWORKSPACE)
|
|
||||||
+ return {.success = false, .error = "selected workspace is not open"};
|
|
||||||
+
|
|
||||||
+ const auto WINDOW = windowToBringFromWorkspace(SOURCEWORKSPACE);
|
|
||||||
+ if (!WINDOW)
|
|
||||||
+ return {.success = false, .error = "selected workspace has no mapped windows"};
|
|
||||||
+
|
|
||||||
+ g_pCompositor->moveWindowToWorkspaceSafe(WINDOW, MONITOR->m_activeWorkspace);
|
|
||||||
+ FOCUSSTATE->fullWindowFocus(WINDOW);
|
|
||||||
+ g_pCompositor->warpCursorTo(WINDOW->middle());
|
|
||||||
+ return {};
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
static SDispatchResult onExpoDispatcher(std::string arg) {
|
|
||||||
|
|
||||||
if (g_pOverview && g_pOverview->m_isSwiping)
|
|
||||||
@@ -77,6 +118,15 @@ static SDispatchResult onExpoDispatcher(std::string arg) {
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
+ if (arg == "bring") {
|
|
||||||
+ if (g_pOverview) {
|
|
||||||
+ g_pOverview->selectHoveredWorkspace();
|
|
||||||
+ const auto BRINGRESULT = bringWindowFromWorkspace(g_pOverview->selectedWorkspaceID());
|
|
||||||
+ g_pOverview->close(false);
|
|
||||||
+ return BRINGRESULT;
|
|
||||||
+ }
|
|
||||||
+ return {};
|
|
||||||
+ }
|
|
||||||
if (arg == "toggle") {
|
|
||||||
if (g_pOverview)
|
|
||||||
g_pOverview->close();
|
|
||||||
diff --git a/overview.cpp b/overview.cpp
|
|
||||||
index 926a9f8..45ee982 100644
|
|
||||||
--- a/overview.cpp
|
|
||||||
+++ b/overview.cpp
|
|
||||||
@@ -343,6 +343,14 @@ void COverview::selectHoveredWorkspace() {
|
|
||||||
closeOnID = x + y * SIDE_LENGTH;
|
|
||||||
}
|
|
||||||
|
|
||||||
+int64_t COverview::selectedWorkspaceID() const {
|
|
||||||
+ const int ID = closeOnID == -1 ? openedID : closeOnID;
|
|
||||||
+ if (ID < 0 || ID >= (int)images.size())
|
|
||||||
+ return WORKSPACE_INVALID;
|
|
||||||
+
|
|
||||||
+ return images[ID].workspaceID;
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
void COverview::redrawID(int id, bool forcelowres) {
|
|
||||||
if (!pMonitor)
|
|
||||||
return;
|
|
||||||
@@ -451,7 +459,7 @@ void COverview::onDamageReported() {
|
|
||||||
g_pCompositor->scheduleFrameForMonitor(pMonitor.lock());
|
|
||||||
}
|
|
||||||
|
|
||||||
-void COverview::close() {
|
|
||||||
+void COverview::close(bool switchToSelection) {
|
|
||||||
if (closing)
|
|
||||||
return;
|
|
||||||
|
|
||||||
@@ -471,7 +479,7 @@ void COverview::close() {
|
|
||||||
|
|
||||||
redrawAll();
|
|
||||||
|
|
||||||
- if (TILE.workspaceID != pMonitor->activeWorkspaceID()) {
|
|
||||||
+ if (switchToSelection && TILE.workspaceID != pMonitor->activeWorkspaceID()) {
|
|
||||||
pMonitor->setSpecialWorkspace(0);
|
|
||||||
|
|
||||||
// If this tile's workspace was WORKSPACE_INVALID, move to the next
|
|
||||||
diff --git a/overview.hpp b/overview.hpp
|
|
||||||
index 1f6bf3c..ca59f32 100644
|
|
||||||
--- a/overview.hpp
|
|
||||||
+++ b/overview.hpp
|
|
||||||
@@ -33,8 +33,9 @@ class COverview {
|
|
||||||
void onSwipeEnd();
|
|
||||||
|
|
||||||
// close without a selection
|
|
||||||
- void close();
|
|
||||||
+ void close(bool switchToSelection = true);
|
|
||||||
void selectHoveredWorkspace();
|
|
||||||
+ int64_t selectedWorkspaceID() const;
|
|
||||||
|
|
||||||
bool blockOverviewRendering = false;
|
|
||||||
bool blockDamageReporting = false;
|
|
||||||
--
|
|
||||||
2.52.0
|
|
||||||
|
|
||||||
200
nixos/remote-hyprland.nix
Normal file
200
nixos/remote-hyprland.nix
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
{ config, lib, pkgs, makeEnable, ... }:
|
||||||
|
let
|
||||||
|
cfg = config.myModules.remote-hyprland;
|
||||||
|
hyprlandPackage = config.programs.hyprland.package;
|
||||||
|
geometry = "${toString cfg.width}x${toString cfg.height}@${toString cfg.refreshRate}";
|
||||||
|
monitorRule = "${cfg.output},${geometry},0x0,${toString cfg.scale}";
|
||||||
|
remoteHyprlandStartVnc = pkgs.writeShellScript "remote-hyprland-start-vnc" ''
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
export WAYLAND_DISPLAY=${cfg.socket}
|
||||||
|
export XDG_CURRENT_DESKTOP=Hyprland
|
||||||
|
export XDG_SESSION_DESKTOP=Hyprland
|
||||||
|
export XDG_SESSION_TYPE=wayland
|
||||||
|
|
||||||
|
for _ in $(${pkgs.coreutils}/bin/seq 1 50); do
|
||||||
|
if ${hyprlandPackage}/bin/hyprctl -j monitors >/dev/null 2>&1; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
${pkgs.coreutils}/bin/sleep 0.1
|
||||||
|
done
|
||||||
|
|
||||||
|
# Give wayvnc a stable output name instead of relying on Hyprland's
|
||||||
|
# fallback HEADLESS-* naming.
|
||||||
|
${hyprlandPackage}/bin/hyprctl output create headless ${cfg.output} >/dev/null 2>&1 || true
|
||||||
|
${hyprlandPackage}/bin/hyprctl keyword monitor '${monitorRule}' >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
exec ${pkgs.wayvnc}/bin/wayvnc \
|
||||||
|
--log-level=info \
|
||||||
|
--output ${cfg.output} \
|
||||||
|
${cfg.bindAddress} ${toString cfg.port}
|
||||||
|
'';
|
||||||
|
remoteHyprlandConfig = pkgs.writeText "remote-hyprland.conf" ''
|
||||||
|
monitor=${monitorRule}
|
||||||
|
monitor=,${geometry},0x0,${toString cfg.scale}
|
||||||
|
|
||||||
|
env = XDG_CURRENT_DESKTOP,Hyprland
|
||||||
|
env = XDG_SESSION_DESKTOP,Hyprland
|
||||||
|
env = XDG_SESSION_TYPE,wayland
|
||||||
|
|
||||||
|
input {
|
||||||
|
kb_layout = us
|
||||||
|
follow_mouse = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
general {
|
||||||
|
gaps_in = 4
|
||||||
|
gaps_out = 8
|
||||||
|
border_size = 2
|
||||||
|
layout = dwindle
|
||||||
|
}
|
||||||
|
|
||||||
|
decoration {
|
||||||
|
rounding = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
dwindle {
|
||||||
|
pseudotile = true
|
||||||
|
preserve_split = true
|
||||||
|
}
|
||||||
|
|
||||||
|
misc {
|
||||||
|
disable_hyprland_logo = true
|
||||||
|
disable_splash_rendering = true
|
||||||
|
force_default_wallpaper = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
$mainMod = SUPER
|
||||||
|
bind = $mainMod, Return, exec, ${cfg.terminalCommand}
|
||||||
|
bind = $mainMod, D, exec, ${pkgs.rofi}/bin/rofi -show drun
|
||||||
|
bind = $mainMod, Q, killactive
|
||||||
|
bind = $mainMod SHIFT, M, exit
|
||||||
|
|
||||||
|
bind = $mainMod, H, movefocus, l
|
||||||
|
bind = $mainMod, J, movefocus, d
|
||||||
|
bind = $mainMod, K, movefocus, u
|
||||||
|
bind = $mainMod, L, movefocus, r
|
||||||
|
|
||||||
|
bind = $mainMod SHIFT, H, movewindow, l
|
||||||
|
bind = $mainMod SHIFT, J, movewindow, d
|
||||||
|
bind = $mainMod SHIFT, K, movewindow, u
|
||||||
|
bind = $mainMod SHIFT, L, movewindow, r
|
||||||
|
|
||||||
|
bind = $mainMod, 1, workspace, 1
|
||||||
|
bind = $mainMod, 2, workspace, 2
|
||||||
|
bind = $mainMod, 3, workspace, 3
|
||||||
|
bind = $mainMod, 4, workspace, 4
|
||||||
|
bind = $mainMod, 5, workspace, 5
|
||||||
|
bind = $mainMod, 6, workspace, 6
|
||||||
|
bind = $mainMod, 7, workspace, 7
|
||||||
|
bind = $mainMod, 8, workspace, 8
|
||||||
|
bind = $mainMod, 9, workspace, 9
|
||||||
|
|
||||||
|
bind = $mainMod SHIFT, 1, movetoworkspace, 1
|
||||||
|
bind = $mainMod SHIFT, 2, movetoworkspace, 2
|
||||||
|
bind = $mainMod SHIFT, 3, movetoworkspace, 3
|
||||||
|
bind = $mainMod SHIFT, 4, movetoworkspace, 4
|
||||||
|
bind = $mainMod SHIFT, 5, movetoworkspace, 5
|
||||||
|
bind = $mainMod SHIFT, 6, movetoworkspace, 6
|
||||||
|
bind = $mainMod SHIFT, 7, movetoworkspace, 7
|
||||||
|
bind = $mainMod SHIFT, 8, movetoworkspace, 8
|
||||||
|
bind = $mainMod SHIFT, 9, movetoworkspace, 9
|
||||||
|
|
||||||
|
exec-once = ${remoteHyprlandStartVnc}
|
||||||
|
exec-once = ${cfg.terminalCommand}
|
||||||
|
'';
|
||||||
|
enabledModule = makeEnable config "myModules.remote-hyprland" false {
|
||||||
|
myModules.hyprland.enable = true;
|
||||||
|
|
||||||
|
users.manageLingering = true;
|
||||||
|
users.users.${cfg.user}.linger = true;
|
||||||
|
|
||||||
|
environment.systemPackages = [ pkgs.wayvnc ];
|
||||||
|
|
||||||
|
home-manager.users.${cfg.user}.systemd.user.services.remote-hyprland = {
|
||||||
|
Unit = {
|
||||||
|
Description = "Headless Hyprland session for remote VNC access";
|
||||||
|
After = [ "default.target" ];
|
||||||
|
};
|
||||||
|
Service = {
|
||||||
|
ExecStart = "${hyprlandPackage}/bin/Hyprland --socket ${cfg.socket} --config ${remoteHyprlandConfig}";
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = 5;
|
||||||
|
Environment = [
|
||||||
|
"XDG_CURRENT_DESKTOP=Hyprland"
|
||||||
|
"XDG_SESSION_DESKTOP=Hyprland"
|
||||||
|
"XDG_SESSION_TYPE=wayland"
|
||||||
|
"WAYLAND_DISPLAY=${cfg.socket}"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
Install = {
|
||||||
|
WantedBy = [ "default.target" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
enabledModule // {
|
||||||
|
options = lib.recursiveUpdate enabledModule.options {
|
||||||
|
myModules.remote-hyprland = {
|
||||||
|
user = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "imalison";
|
||||||
|
description = "User account that owns the remote Hyprland session.";
|
||||||
|
};
|
||||||
|
|
||||||
|
bindAddress = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "127.0.0.1";
|
||||||
|
description = "Address for wayvnc to bind. Keep localhost when using SSH or Tailscale forwarding.";
|
||||||
|
};
|
||||||
|
|
||||||
|
port = lib.mkOption {
|
||||||
|
type = lib.types.port;
|
||||||
|
default = 5900;
|
||||||
|
description = "TCP port for wayvnc.";
|
||||||
|
};
|
||||||
|
|
||||||
|
socket = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "wayland-remote-hyprland";
|
||||||
|
description = "Wayland socket name used by the remote Hyprland instance.";
|
||||||
|
};
|
||||||
|
|
||||||
|
output = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "remote";
|
||||||
|
description = "Stable Hyprland headless output name captured by wayvnc.";
|
||||||
|
};
|
||||||
|
|
||||||
|
width = lib.mkOption {
|
||||||
|
type = lib.types.ints.positive;
|
||||||
|
default = 1920;
|
||||||
|
description = "Remote output width.";
|
||||||
|
};
|
||||||
|
|
||||||
|
height = lib.mkOption {
|
||||||
|
type = lib.types.ints.positive;
|
||||||
|
default = 1080;
|
||||||
|
description = "Remote output height.";
|
||||||
|
};
|
||||||
|
|
||||||
|
refreshRate = lib.mkOption {
|
||||||
|
type = lib.types.ints.positive;
|
||||||
|
default = 60;
|
||||||
|
description = "Remote output refresh rate.";
|
||||||
|
};
|
||||||
|
|
||||||
|
scale = lib.mkOption {
|
||||||
|
type = lib.types.number;
|
||||||
|
default = 1;
|
||||||
|
description = "Remote output scale.";
|
||||||
|
};
|
||||||
|
|
||||||
|
terminalCommand = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "${pkgs.ghostty}/bin/ghostty --gtk-single-instance=false";
|
||||||
|
description = "Command launched for the default terminal binding and initial window.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -2,17 +2,19 @@
|
|||||||
makeEnable,
|
makeEnable,
|
||||||
config,
|
config,
|
||||||
...
|
...
|
||||||
}: let
|
}:
|
||||||
|
let
|
||||||
shared = import ../nix-shared/syncthing.nix;
|
shared = import ../nix-shared/syncthing.nix;
|
||||||
inherit (shared) devices allDevices;
|
inherit (shared) devices allDevices;
|
||||||
in
|
in
|
||||||
makeEnable config "myModules.syncthing" true {
|
makeEnable config "myModules.syncthing" true {
|
||||||
system.activationScripts.syncthingPermissions = {
|
system.activationScripts.syncthingPermissions = {
|
||||||
text = ''
|
text = ''
|
||||||
|
mkdir -p /var/lib/syncthing/sync
|
||||||
|
mkdir -p /var/lib/syncthing/sync/Screensaver/use
|
||||||
|
mkdir -p /var/lib/syncthing/railbird
|
||||||
chown -R syncthing:syncthing /var/lib/syncthing
|
chown -R syncthing:syncthing /var/lib/syncthing
|
||||||
chmod -R 2770 /var/lib/syncthing
|
chmod -R 2770 /var/lib/syncthing
|
||||||
mkdir -p /var/lib/syncthing/sync
|
|
||||||
mkdir -p /var/lib/syncthing/railbird
|
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
systemd.services.syncthing = {
|
systemd.services.syncthing = {
|
||||||
|
|||||||
@@ -56,41 +56,29 @@ let
|
|||||||
|
|
||||||
exec ${taffybarPackage}/bin/taffybar "$@"
|
exec ${taffybarPackage}/bin/taffybar "$@"
|
||||||
'';
|
'';
|
||||||
skipTaffybarInKde = pkgs.writeShellScript "skip-taffybar-in-kde" ''
|
skipTaffybarInOtherShells = pkgs.writeShellScript "skip-taffybar-in-other-shells" ''
|
||||||
current_desktop="''${XDG_CURRENT_DESKTOP:-}"
|
current_desktop="''${XDG_CURRENT_DESKTOP:-}"
|
||||||
desktop_session="''${DESKTOP_SESSION:-}"
|
desktop_session="''${DESKTOP_SESSION:-}"
|
||||||
|
|
||||||
case "''${current_desktop}:''${desktop_session}" in
|
case "''${current_desktop}:''${desktop_session}" in
|
||||||
*KDE*|*kde*|*Plasma*|*plasma*) exit 1 ;;
|
*KDE*|*kde*|*Plasma*|*plasma*) exit 1 ;;
|
||||||
*) exit 0 ;;
|
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
exit 0
|
||||||
|
'';
|
||||||
|
taffybarExecCondition = pkgs.writeShellScript "taffybar-exec-condition" ''
|
||||||
|
${skipTaffybarInOtherShells} || exit 1
|
||||||
|
|
||||||
|
if [ -x /run/current-system/sw/bin/desktop_shell_ui ]; then
|
||||||
|
exec /run/current-system/sw/bin/desktop_shell_ui exec-condition taffybar
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
'';
|
'';
|
||||||
in
|
in
|
||||||
makeEnable config "myModules.taffybar" false {
|
makeEnable config "myModules.taffybar" false {
|
||||||
myModules.sni.enable = true;
|
myModules.sni.enable = true;
|
||||||
|
|
||||||
nixpkgs.overlays = with inputs; (
|
|
||||||
if builtins.isList taffybar.overlays
|
|
||||||
then taffybar.overlays
|
|
||||||
else builtins.attrValues taffybar.overlays
|
|
||||||
) ++ [
|
|
||||||
# status-notifier-item's test suite spawns `dbus-daemon`; ensure it's on PATH.
|
|
||||||
(final: prev: {
|
|
||||||
haskellPackages = prev.haskellPackages.override (old: {
|
|
||||||
overrides =
|
|
||||||
final.lib.composeExtensions (old.overrides or (_: _: {}))
|
|
||||||
(hself: hsuper: {
|
|
||||||
status-notifier-item = hsuper.status-notifier-item.overrideAttrs (oldAttrs: {
|
|
||||||
checkInputs = (oldAttrs.checkInputs or []) ++ [ prev.dbus ];
|
|
||||||
# The test suite assumes a system-wide /etc/dbus-1/session.conf,
|
|
||||||
# which isn't present in Nix sandboxes.
|
|
||||||
doCheck = false;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
];
|
|
||||||
|
|
||||||
environment.systemPackages = [
|
environment.systemPackages = [
|
||||||
taffybarPackage
|
taffybarPackage
|
||||||
];
|
];
|
||||||
@@ -121,7 +109,7 @@ makeEnable config "myModules.taffybar" false {
|
|||||||
rmdir --ignore-fail-on-non-empty "$HOME/.config/systemd/user/taffybar.service.d" 2>/dev/null || true
|
rmdir --ignore-fail-on-non-empty "$HOME/.config/systemd/user/taffybar.service.d" 2>/dev/null || true
|
||||||
'';
|
'';
|
||||||
systemd.user.services.taffybar.Service = {
|
systemd.user.services.taffybar.Service = {
|
||||||
ExecCondition = "${skipTaffybarInKde}";
|
ExecCondition = "${taffybarExecCondition}";
|
||||||
ExecStart = lib.mkForce "${taffybarStart}";
|
ExecStart = lib.mkForce "${taffybarStart}";
|
||||||
# Temporary startup debugging: keep a plain-text log outside journald so
|
# Temporary startup debugging: keep a plain-text log outside journald so
|
||||||
# the next login/startup leaves easy-to-inspect tray traces behind.
|
# the next login/startup leaves easy-to-inspect tray traces behind.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{ config, pkgs, inputs, makeEnable, ... }:
|
{ config, pkgs, inputs, lib, makeEnable, ... }:
|
||||||
makeEnable config "myModules.xmonad" true {
|
makeEnable config "myModules.xmonad" true {
|
||||||
myModules.taffybar.enable = true;
|
myModules.taffybar.enable = lib.mkDefault (config.myModules.desktop.shellUi == "taffybar");
|
||||||
|
|
||||||
nixpkgs.overlays = with inputs; [
|
nixpkgs.overlays = with inputs; [
|
||||||
xmonad.overlay
|
xmonad.overlay
|
||||||
|
|||||||
Reference in New Issue
Block a user