Split Hyprland Lua config into modules
This commit is contained in:
@@ -137,8 +137,9 @@ notifications, SNI/tray support, fonts, and app defaults.
|
||||
|
||||
The currently important pieces are:
|
||||
|
||||
- Hyprland configuration in [[file:dotfiles/config/hypr/hyprland.lua][dotfiles/config/hypr/hyprland.lua]], backed by custom
|
||||
plugin inputs in the NixOS flake.
|
||||
- Hyprland configuration in [[file:dotfiles/config/hypr/hyprland.lua][dotfiles/config/hypr/hyprland.lua]], with imported Lua
|
||||
modules under [[file:dotfiles/config/hypr/hyprland/][dotfiles/config/hypr/hyprland/]], backed by custom plugin inputs in
|
||||
the NixOS flake.
|
||||
- XMonad configuration in [[file:dotfiles/config/xmonad/xmonad.hs][dotfiles/config/xmonad/xmonad.hs]], with upstream
|
||||
=xmonad= and =xmonad-contrib= available as submodules/checkouts.
|
||||
- Taffybar configuration in [[file:dotfiles/config/taffybar/taffybar.hs][dotfiles/config/taffybar/taffybar.hs]], plus a local
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
269
dotfiles/config/hypr/hyprland/binds.lua
Normal file
269
dotfiles/config/hypr/hyprland/binds.lua
Normal file
@@ -0,0 +1,269 @@
|
||||
local M = {}
|
||||
|
||||
function M.setup(ctx)
|
||||
local _ENV = ctx
|
||||
bind(main_mod .. " + P", exec(launcher_command))
|
||||
bind(main_mod .. " + SHIFT + P", exec(run_menu))
|
||||
bind(hyper .. " + SHIFT + N", exec(shell_ui_command .. " control-center"))
|
||||
bind(hyper .. " + CTRL + N", exec(shell_ui_command .. " settings"))
|
||||
bind(main_mod .. " + SHIFT + Return", exec(terminal))
|
||||
bind(main_mod .. " + Q", exec("hyprctl reload"))
|
||||
bind(main_mod .. " + SHIFT + C", hl.dsp.window.close())
|
||||
bind(main_mod .. " + SHIFT + Q", hl.dsp.exit())
|
||||
bind(main_mod .. " + E", exec("emacsclient --eval '(emacs-everywhere)'"))
|
||||
bind(main_mod .. " + V", exec("wl-paste | xdotool type --file -"))
|
||||
bind(main_mod .. " + Tab", hyprwinview({
|
||||
action = "show",
|
||||
start_in_filter_mode = true,
|
||||
default_action = "bring",
|
||||
}), overview_bind_opts)
|
||||
bind(main_mod .. " + SHIFT + Tab", hyprwinview({
|
||||
action = "show",
|
||||
include_current_workspace = false,
|
||||
start_in_filter_mode = true,
|
||||
default_action = "bring",
|
||||
}), overview_bind_opts)
|
||||
bind(main_mod .. " + SHIFT + slash", hyprwinview({ action = "toggle-filter" }), overview_bind_opts)
|
||||
bind("ALT + Tab", hyprexpo("open"), overview_bind_opts)
|
||||
bind("ALT + SHIFT + Tab", hyprexpo("bring"), overview_bind_opts)
|
||||
bind(main_mod .. " + G", hyprwinview({
|
||||
action = "show",
|
||||
start_in_filter_mode = true,
|
||||
default_action = "select",
|
||||
}), overview_bind_opts)
|
||||
bind(main_mod .. " + B", hyprwinview({
|
||||
action = "show",
|
||||
start_in_filter_mode = true,
|
||||
default_action = "bring",
|
||||
}), overview_bind_opts)
|
||||
bind(main_mod .. " + SHIFT + B", hyprwinview({
|
||||
action = "show",
|
||||
start_in_filter_mode = true,
|
||||
default_action = "bring-replace",
|
||||
}), overview_bind_opts)
|
||||
|
||||
bind(main_mod .. " + W", function()
|
||||
focus_direction("up")
|
||||
end)
|
||||
bind(main_mod .. " + S", function()
|
||||
focus_direction("down")
|
||||
end)
|
||||
bind(main_mod .. " + A", function()
|
||||
focus_direction("left")
|
||||
end)
|
||||
bind(main_mod .. " + D", function()
|
||||
focus_direction("right")
|
||||
end)
|
||||
|
||||
bind(main_mod .. " + SHIFT + W", function()
|
||||
swap_direction("up")
|
||||
end)
|
||||
bind(main_mod .. " + SHIFT + S", function()
|
||||
swap_direction("down")
|
||||
end)
|
||||
bind(main_mod .. " + SHIFT + A", function()
|
||||
swap_direction("left")
|
||||
end)
|
||||
bind(main_mod .. " + SHIFT + D", function()
|
||||
swap_direction("right")
|
||||
end)
|
||||
|
||||
bind(main_mod .. " + CTRL + W", function()
|
||||
move_window_to_monitor("u", false)
|
||||
end)
|
||||
bind(main_mod .. " + CTRL + S", function()
|
||||
move_window_to_monitor("d", false)
|
||||
end)
|
||||
bind(main_mod .. " + CTRL + A", function()
|
||||
move_window_to_monitor("l", false)
|
||||
end)
|
||||
bind(main_mod .. " + CTRL + D", function()
|
||||
move_window_to_monitor("r", false)
|
||||
end)
|
||||
bind(main_mod .. " + CTRL + SHIFT + W", function()
|
||||
move_window_to_empty_workspace_on_monitor("u")
|
||||
end)
|
||||
bind(main_mod .. " + CTRL + SHIFT + S", function()
|
||||
move_window_to_empty_workspace_on_monitor("d")
|
||||
end)
|
||||
bind(main_mod .. " + CTRL + SHIFT + A", function()
|
||||
move_window_to_empty_workspace_on_monitor("l")
|
||||
end)
|
||||
bind(main_mod .. " + CTRL + SHIFT + D", function()
|
||||
move_window_to_empty_workspace_on_monitor("r")
|
||||
end)
|
||||
|
||||
hl.define_submap("swap-workspace", function()
|
||||
for i = 1, 9 do
|
||||
local workspace_id = i
|
||||
bind(tostring(i), function()
|
||||
swap_current_workspace_with(workspace_id)
|
||||
dispatch(hl.dsp.submap("reset"))
|
||||
end)
|
||||
end
|
||||
|
||||
bind("Escape", hl.dsp.submap("reset"))
|
||||
bind("catchall", hl.dsp.submap("reset"))
|
||||
end)
|
||||
|
||||
hl.define_submap("window-picker", function()
|
||||
for i = 1, 9 do
|
||||
local index = i
|
||||
bind(tostring(i), function()
|
||||
activate_window_picker_candidate(index)
|
||||
end)
|
||||
end
|
||||
|
||||
bind("Escape", hl.dsp.submap("reset"))
|
||||
bind("catchall", hl.dsp.submap("reset"))
|
||||
end)
|
||||
|
||||
bind(mod_alt .. " + SHIFT + W", hl.dsp.window.resize({ x = 0, y = -50, relative = true }), { repeating = true })
|
||||
bind(mod_alt .. " + SHIFT + S", hl.dsp.window.resize({ x = 0, y = 50, relative = true }), { repeating = true })
|
||||
bind(mod_alt .. " + SHIFT + A", hl.dsp.window.resize({ x = -50, y = 0, relative = true }), { repeating = true })
|
||||
bind(mod_alt .. " + SHIFT + D", hl.dsp.window.resize({ x = 50, y = 0, relative = true }), { repeating = true })
|
||||
|
||||
bind(hyper .. " + W", hl.dsp.focus({ monitor = "u" }))
|
||||
bind(hyper .. " + S", hl.dsp.focus({ monitor = "d" }))
|
||||
bind(hyper .. " + A", hl.dsp.focus({ monitor = "l" }))
|
||||
bind(hyper .. " + D", hl.dsp.focus({ monitor = "r" }))
|
||||
bind(hyper .. " + SHIFT + W", function()
|
||||
move_window_to_monitor("u", true)
|
||||
end)
|
||||
bind(hyper .. " + SHIFT + S", function()
|
||||
move_window_to_monitor("d", true)
|
||||
end)
|
||||
bind(hyper .. " + SHIFT + A", function()
|
||||
move_window_to_monitor("l", true)
|
||||
end)
|
||||
bind(hyper .. " + SHIFT + D", function()
|
||||
move_window_to_monitor("r", true)
|
||||
end)
|
||||
|
||||
bind(main_mod .. " + Space", cycle_layout_or_restore_tabbed_group)
|
||||
bind(main_mod .. " + SHIFT + Space", force_columns_layout)
|
||||
bind(main_mod .. " + CTRL + Space", gather_workspace_into_tabbed_group)
|
||||
bind(main_mod .. " + bracketright", monocle_next)
|
||||
bind(main_mod .. " + bracketleft", monocle_prev)
|
||||
bind(main_mod .. " + T", hl.dsp.window.float({ action = "disable" }))
|
||||
bind(main_mod .. " + O", toggle_pinned_active_window)
|
||||
bind(main_mod .. " + M", minimize_active_window)
|
||||
bind(main_mod .. " + SHIFT + M", restore_last_minimized)
|
||||
bind(main_mod .. " + CTRL + SHIFT + M", function()
|
||||
enter_window_picker("minimized")
|
||||
end)
|
||||
bind(main_mod .. " + SHIFT + equal", schedule_nstack_count_update)
|
||||
bind(main_mod .. " + CTRL + M", hl.dsp.window.toggle_swallow())
|
||||
bind(main_mod .. " + SHIFT + E", function()
|
||||
move_to_next_empty_workspace(true)
|
||||
end)
|
||||
bind(main_mod .. " + CTRL + E", function()
|
||||
move_to_next_empty_workspace(false)
|
||||
end)
|
||||
bind(main_mod .. " + apostrophe", focus_next_class)
|
||||
bind(mod_alt .. " + W", show_active_window_info)
|
||||
|
||||
bind(main_mod .. " + X", exec("rofi_command.sh"))
|
||||
bind(main_mod .. " + SHIFT + X", hl.dsp.workspace.toggle_special("NSP"))
|
||||
bind(mod_alt .. " + C", function()
|
||||
toggle_scratchpad("codex")
|
||||
end)
|
||||
bind(mod_alt .. " + E", function()
|
||||
toggle_scratchpad("element")
|
||||
end)
|
||||
bind(mod_alt .. " + H", function()
|
||||
toggle_scratchpad("htop")
|
||||
end)
|
||||
bind(mod_alt .. " + K", function()
|
||||
toggle_scratchpad("slack")
|
||||
end)
|
||||
bind(mod_alt .. " + M", function()
|
||||
toggle_scratchpad("messages")
|
||||
end)
|
||||
bind(mod_alt .. " + S", function()
|
||||
toggle_scratchpad("spotify")
|
||||
end)
|
||||
bind(mod_alt .. " + T", function()
|
||||
toggle_scratchpad("transmission")
|
||||
end)
|
||||
bind(mod_alt .. " + V", function()
|
||||
toggle_scratchpad("volume")
|
||||
end)
|
||||
bind(mod_alt .. " + grave", function()
|
||||
toggle_scratchpad("dropdown")
|
||||
end)
|
||||
bind(mod_alt .. " + Space", minimize_other_classes)
|
||||
bind(mod_alt .. " + SHIFT + Space", restore_focused_class)
|
||||
bind(mod_alt .. " + Return", restore_all_minimized)
|
||||
|
||||
for i = 1, 9 do
|
||||
local workspace = tostring(i)
|
||||
bind(main_mod .. " + " .. workspace, hl.dsp.focus({ workspace = workspace, on_current_monitor = true }))
|
||||
bind(main_mod .. " + SHIFT + " .. workspace, hl.dsp.window.move({ workspace = workspace, follow = false }))
|
||||
bind(main_mod .. " + CTRL + " .. workspace, function()
|
||||
dispatch(hl.dsp.window.move({ workspace = workspace, follow = false }))
|
||||
dispatch(hl.dsp.focus({ workspace = workspace, on_current_monitor = true }))
|
||||
end)
|
||||
end
|
||||
|
||||
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 .. " + SHIFT + Z", hl.dsp.window.move({ monitor = "+1" }))
|
||||
bind(main_mod .. " + mouse_down", function()
|
||||
cycle_workspace(1)
|
||||
end)
|
||||
bind(main_mod .. " + mouse_up", function()
|
||||
cycle_workspace(-1)
|
||||
end)
|
||||
bind(hyper .. " + E", focus_next_empty_workspace)
|
||||
bind(hyper .. " + 5", enter_workspace_swap_mode)
|
||||
bind(hyper .. " + G", gather_focused_class)
|
||||
|
||||
bind(main_mod .. " + I", exec("set_volume --unmute --change-volume +5"), { repeating = true })
|
||||
bind(main_mod .. " + K", exec("set_volume --unmute --change-volume -5"), { repeating = true })
|
||||
bind(main_mod .. " + U", exec("set_volume --toggle-mute"))
|
||||
bind(main_mod .. " + semicolon", exec("playerctl play-pause"))
|
||||
bind(main_mod .. " + L", exec("playerctl next"))
|
||||
bind(main_mod .. " + J", exec("playerctl previous"))
|
||||
|
||||
bind("XF86AudioPlay", exec("playerctl play-pause"))
|
||||
bind("XF86AudioPause", exec("playerctl play-pause"))
|
||||
bind("XF86AudioNext", exec("playerctl next"))
|
||||
bind("XF86AudioPrev", exec("playerctl previous"))
|
||||
bind("XF86AudioRaiseVolume", exec("set_volume --unmute --change-volume +5"), { repeating = true })
|
||||
bind("XF86AudioLowerVolume", exec("set_volume --unmute --change-volume -5"), { repeating = true })
|
||||
bind("XF86AudioMute", exec("set_volume --toggle-mute"))
|
||||
bind("XF86MonBrightnessUp", exec("brightness.sh up"), { repeating = true })
|
||||
bind("XF86MonBrightnessDown", exec("brightness.sh down"), { repeating = true })
|
||||
|
||||
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("rofi_tmcodex.sh"))
|
||||
bind(hyper .. " + SHIFT + C", exec("rofi_tmcodex.sh resume"))
|
||||
bind(hyper .. " + SHIFT + L", exec("hyprlock"))
|
||||
bind(hyper .. " + L", exec("hypr_rofi_layout"))
|
||||
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", function()
|
||||
hl.exec_cmd("toggle_taffybar")
|
||||
refresh_monitor_reserved_cache(0.25)
|
||||
refresh_active_scratchpad_geometries_later(600)
|
||||
end)
|
||||
bind(hyper .. " + I", exec("rofi_select_input.hs"))
|
||||
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 .. " + comma", exec("rofi_wallpaper.sh"))
|
||||
bind(hyper .. " + SHIFT + comma", exec("/home/imalison/dotfiles/dotfiles/lib/bin/neowall-wallpaper toggle"))
|
||||
bind(hyper .. " + Y", exec("rofi_agentic_skill"))
|
||||
bind(main_mod .. " + R", exec("hyprctl reload"))
|
||||
|
||||
bind(main_mod .. " + mouse:272", float_and_drag_active_window)
|
||||
bind(main_mod .. " + mouse:273", float_and_resize_active_window)
|
||||
end
|
||||
|
||||
return M
|
||||
584
dotfiles/config/hypr/hyprland/core.lua
Normal file
584
dotfiles/config/hypr/hyprland/core.lua
Normal file
@@ -0,0 +1,584 @@
|
||||
local M = {}
|
||||
|
||||
function M.setup(ctx)
|
||||
local _ENV = ctx
|
||||
local function command_line_contains(needle)
|
||||
local command_line = io.open("/proc/self/cmdline", "rb")
|
||||
if not command_line then
|
||||
return false
|
||||
end
|
||||
|
||||
local contents = command_line:read("*a") or ""
|
||||
command_line:close()
|
||||
return contents:find(needle, 1, true) ~= nil
|
||||
end
|
||||
|
||||
verify_config = command_line_contains("--verify-config")
|
||||
|
||||
local function bind(keys, dispatcher, opts)
|
||||
hl.bind(keys, dispatcher, opts)
|
||||
end
|
||||
|
||||
local function exec(command)
|
||||
return hl.dsp.exec_cmd(command)
|
||||
end
|
||||
|
||||
local function dispatch(dispatcher)
|
||||
return hl.dispatch(dispatcher)
|
||||
end
|
||||
|
||||
local function shell_quote(value)
|
||||
return "'" .. tostring(value):gsub("'", "'\\''") .. "'"
|
||||
end
|
||||
|
||||
local function overview_trace(label)
|
||||
local enabled = io.open(overview_trace_enabled_path, "r")
|
||||
if not enabled then
|
||||
return
|
||||
end
|
||||
enabled:close()
|
||||
|
||||
local trace = io.open(overview_trace_path, "a")
|
||||
if trace then
|
||||
trace:write(os.date("%Y-%m-%d %H:%M:%S "), label, "\n")
|
||||
trace:close()
|
||||
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)
|
||||
action = action or "toggle"
|
||||
return function()
|
||||
overview_trace("hyprexpo " .. tostring(action))
|
||||
if hl.plugin and hl.plugin.hyprexpo and hl.plugin.hyprexpo.expo then
|
||||
hl.plugin.hyprexpo.expo(action)
|
||||
else
|
||||
hl.notification.create({
|
||||
text = "hyprexpo is not loaded",
|
||||
duration = 1800,
|
||||
icon = notification_icons.warning,
|
||||
color = "rgba(edb443ff)",
|
||||
font_size = 13,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function hyprwinview(action)
|
||||
return function()
|
||||
local label = "hyprwinview"
|
||||
if type(action) == "table" and action.action then
|
||||
label = label .. " " .. tostring(action.action)
|
||||
elseif type(action) ~= "table" and action ~= nil then
|
||||
label = label .. " " .. tostring(action)
|
||||
end
|
||||
|
||||
local function invoke()
|
||||
overview_trace(label)
|
||||
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 = notification_icons.warning,
|
||||
color = "rgba(edb443ff)",
|
||||
font_size = 13,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
invoke()
|
||||
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 = notification_icons.warning,
|
||||
color = "rgba(edb443ff)",
|
||||
font_size = 13,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function apply_nstack_config()
|
||||
if verify_config or not enable_nstack or not configure_nstack_plugin_from_lua then
|
||||
return
|
||||
end
|
||||
|
||||
hl.config({
|
||||
plugin = {
|
||||
nstack = {
|
||||
layout = {
|
||||
orientation = "left",
|
||||
new_on_top = false,
|
||||
new_near_focused = true,
|
||||
new_is_master = false,
|
||||
no_gaps_when_only = true,
|
||||
special_scale_factor = 0.8,
|
||||
inherit_fullscreen = true,
|
||||
stacks = 1,
|
||||
center_single_master = false,
|
||||
mfact = 0.0,
|
||||
single_mfact = 1.0,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
end
|
||||
|
||||
local function apply_hyprexpo_config()
|
||||
if verify_config or not enable_hyprexpo then
|
||||
return
|
||||
end
|
||||
|
||||
hl.config({
|
||||
plugin = {
|
||||
hyprexpo = {
|
||||
columns = 3,
|
||||
gap_size = 5,
|
||||
bg_col = "rgba(111111ff)",
|
||||
workspace_method = "center current",
|
||||
skip_empty = false,
|
||||
max_workspace = max_workspace,
|
||||
show_workspace_numbers = true,
|
||||
workspace_number_color = "rgba(edb443ff)",
|
||||
gesture_distance = 200,
|
||||
},
|
||||
},
|
||||
})
|
||||
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(10101400)",
|
||||
background_blur = 1,
|
||||
border_col = "rgba(ffffff33)",
|
||||
hover_border_col = "rgba(66ccffee)",
|
||||
border_size = 3,
|
||||
window_order = "application",
|
||||
keys_default_action = "return,enter,space,g,f",
|
||||
keys_filter_toggle = "/",
|
||||
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,
|
||||
show_window_text = 1,
|
||||
window_text_font = "Sans",
|
||||
window_text_size = 14,
|
||||
window_text_color = "rgba(ffffffff)",
|
||||
window_text_backplate_col = "rgba(00000099)",
|
||||
window_text_padding = 6,
|
||||
filter_animation_ms = 140,
|
||||
animation = "workspace_zoom",
|
||||
animation_in_ms = 280,
|
||||
animation_out_ms = 220,
|
||||
animation_speed = 1.0,
|
||||
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" },
|
||||
default_action = { "return", "enter", "space", "g", "f" },
|
||||
bring = { "b", "shift+return", "shift+space" },
|
||||
bring_replace = { "shift + b" },
|
||||
close = { "escape", "q" },
|
||||
filter_toggle = { "/" },
|
||||
},
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
local function active_workspace()
|
||||
return hl.get_active_workspace()
|
||||
end
|
||||
|
||||
local function active_workspace_id()
|
||||
local workspace = active_workspace()
|
||||
if workspace and type(workspace.id) == "number" and workspace.id >= 1 then
|
||||
return math.min(max_workspace, math.max(1, workspace.id))
|
||||
end
|
||||
return 1
|
||||
end
|
||||
|
||||
local function workspace_key(workspace)
|
||||
workspace = workspace or active_workspace()
|
||||
if workspace and workspace.id then
|
||||
return tostring(workspace.id)
|
||||
end
|
||||
return tostring(active_workspace_id())
|
||||
end
|
||||
|
||||
local function current_workspace_layout()
|
||||
return workspace_layouts[workspace_key()] or columns_layout
|
||||
end
|
||||
|
||||
local function write_layout_state()
|
||||
local runtime_dir = os.getenv("XDG_RUNTIME_DIR")
|
||||
if not runtime_dir then
|
||||
return
|
||||
end
|
||||
|
||||
local file = io.open(runtime_dir .. "/hyprland-layout-state", "w")
|
||||
if not file then
|
||||
return
|
||||
end
|
||||
|
||||
local workspace = active_workspace()
|
||||
file:write("workspace=", workspace_key(workspace), "\n")
|
||||
file:write("layout=", current_layout, "\n")
|
||||
for key, layout in pairs(workspace_layouts) do
|
||||
file:write("workspace.", tostring(key), "=", tostring(layout), "\n")
|
||||
end
|
||||
file:close()
|
||||
end
|
||||
|
||||
local function is_normal_workspace(workspace)
|
||||
return workspace and not workspace.special and workspace.id and workspace.id >= 1
|
||||
end
|
||||
|
||||
local function lower_contains(value, needle)
|
||||
if not needle or needle == "" then
|
||||
return true
|
||||
end
|
||||
|
||||
value = string.lower(tostring(value or ""))
|
||||
needle = string.lower(tostring(needle))
|
||||
return value:find(needle, 1, true) ~= nil
|
||||
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)
|
||||
return window
|
||||
and lower_contains_any(window.class, def.classes or def.class)
|
||||
and lower_contains(window.title, def.title)
|
||||
end
|
||||
|
||||
local function is_scratchpad_window(window)
|
||||
for _, def in pairs(scratchpads) do
|
||||
if scratchpad_window_matches(window, def) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
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)
|
||||
if not workspace then
|
||||
return false
|
||||
end
|
||||
|
||||
local name = tostring(workspace.name or "")
|
||||
return name == minimized_workspace or name == "minimized" or (workspace.special and name:find("minimized", 1, true) ~= nil)
|
||||
end
|
||||
|
||||
local function is_minimized_window(window)
|
||||
return window and is_minimized_workspace(window.workspace)
|
||||
end
|
||||
|
||||
local function is_normal_window(window)
|
||||
return window
|
||||
and window.mapped ~= false
|
||||
and not window.hidden
|
||||
and window.workspace
|
||||
and is_normal_workspace(window.workspace)
|
||||
and not is_scratchpad_window(window)
|
||||
and not is_minimized_window(window)
|
||||
end
|
||||
|
||||
local function tiled_windows(workspace)
|
||||
local windows = {}
|
||||
if not workspace then
|
||||
return windows
|
||||
end
|
||||
|
||||
for _, window in ipairs(hl.get_workspace_windows(workspace)) do
|
||||
if not window.floating and not window.hidden then
|
||||
windows[#windows + 1] = window
|
||||
end
|
||||
end
|
||||
|
||||
return windows
|
||||
end
|
||||
|
||||
local function tiled_window_count(workspace)
|
||||
return #tiled_windows(workspace)
|
||||
end
|
||||
|
||||
local function sort_windows_by_focus_history(windows)
|
||||
table.sort(windows, function(left, right)
|
||||
return (left.focus_history_id or 0) < (right.focus_history_id or 0)
|
||||
end)
|
||||
end
|
||||
|
||||
local function window_address_set(windows)
|
||||
local addresses = {}
|
||||
for _, window in ipairs(windows) do
|
||||
if window and window.address then
|
||||
addresses[window.address] = true
|
||||
end
|
||||
end
|
||||
return addresses
|
||||
end
|
||||
|
||||
local function window_address_list(windows)
|
||||
local addresses = {}
|
||||
for _, window in ipairs(windows) do
|
||||
if window and window.address then
|
||||
addresses[#addresses + 1] = window.address
|
||||
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 windows_by_address()
|
||||
local windows = {}
|
||||
for _, window in ipairs(hl.get_windows()) do
|
||||
if window and window.address then
|
||||
windows[window.address] = window
|
||||
end
|
||||
end
|
||||
return windows
|
||||
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 tiled_window_geometry(window)
|
||||
if not window or window.floating then
|
||||
return nil
|
||||
end
|
||||
|
||||
local selector = window_selector(window)
|
||||
if not selector then
|
||||
return nil
|
||||
end
|
||||
|
||||
local at = window.at or {}
|
||||
local size = window.size or {}
|
||||
local width = math.floor(numeric_component(size, "x", 1))
|
||||
local height = math.floor(numeric_component(size, "y", 2))
|
||||
if width <= 0 or height <= 0 then
|
||||
return nil
|
||||
end
|
||||
|
||||
return {
|
||||
selector = selector,
|
||||
x = math.floor(numeric_component(at, "x", 1)),
|
||||
y = math.floor(numeric_component(at, "y", 2)),
|
||||
width = width,
|
||||
height = height,
|
||||
}
|
||||
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 sort_windows_by_visual_position(windows)
|
||||
table.sort(windows, function(left, right)
|
||||
local left_x, left_y = window_center(left)
|
||||
local right_x, right_y = window_center(right)
|
||||
|
||||
if math.abs(left_x - right_x) > 10 then
|
||||
return left_x < right_x
|
||||
end
|
||||
if math.abs(left_y - right_y) > 10 then
|
||||
return left_y < right_y
|
||||
end
|
||||
return tostring(left.address or "") < tostring(right.address or "")
|
||||
end)
|
||||
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 workspace = hl.get_workspace(tostring(workspace_id))
|
||||
if not workspace then
|
||||
return 0
|
||||
end
|
||||
return workspace.windows or tiled_window_count(workspace)
|
||||
end
|
||||
|
||||
local function find_empty_workspace(target_monitor, exclude_id)
|
||||
local unused_candidate = nil
|
||||
local elsewhere_empty_candidate = nil
|
||||
local target_monitor_name = target_monitor and target_monitor.name or nil
|
||||
|
||||
for i = 1, max_workspace do
|
||||
if i ~= exclude_id then
|
||||
local workspace = hl.get_workspace(tostring(i))
|
||||
|
||||
if not workspace then
|
||||
unused_candidate = unused_candidate or i
|
||||
elseif is_normal_workspace(workspace) and workspace_window_count(i) == 0 then
|
||||
local monitor = workspace.monitor
|
||||
if target_monitor_name and monitor and monitor.name == target_monitor_name then
|
||||
return i
|
||||
end
|
||||
elsewhere_empty_candidate = elsewhere_empty_candidate or i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return unused_candidate or elsewhere_empty_candidate
|
||||
end
|
||||
|
||||
ctx.command_line_contains = command_line_contains
|
||||
ctx.bind = bind
|
||||
ctx.exec = exec
|
||||
ctx.dispatch = dispatch
|
||||
ctx.shell_quote = shell_quote
|
||||
ctx.overview_trace = overview_trace
|
||||
ctx.window_selector = window_selector
|
||||
ctx.hyprexpo = hyprexpo
|
||||
ctx.hyprwinview = hyprwinview
|
||||
ctx.workspacehistory = workspacehistory
|
||||
ctx.apply_nstack_config = apply_nstack_config
|
||||
ctx.apply_hyprexpo_config = apply_hyprexpo_config
|
||||
ctx.apply_hyprwinview_config = apply_hyprwinview_config
|
||||
ctx.active_workspace = active_workspace
|
||||
ctx.active_workspace_id = active_workspace_id
|
||||
ctx.workspace_key = workspace_key
|
||||
ctx.current_workspace_layout = current_workspace_layout
|
||||
ctx.write_layout_state = write_layout_state
|
||||
ctx.is_normal_workspace = is_normal_workspace
|
||||
ctx.lower_contains = lower_contains
|
||||
ctx.lower_contains_any = lower_contains_any
|
||||
ctx.scratchpad_window_matches = scratchpad_window_matches
|
||||
ctx.is_scratchpad_window = is_scratchpad_window
|
||||
ctx.matching_scratchpad_name = matching_scratchpad_name
|
||||
ctx.same_workspace = same_workspace
|
||||
ctx.is_minimized_workspace = is_minimized_workspace
|
||||
ctx.is_minimized_window = is_minimized_window
|
||||
ctx.is_normal_window = is_normal_window
|
||||
ctx.tiled_windows = tiled_windows
|
||||
ctx.tiled_window_count = tiled_window_count
|
||||
ctx.sort_windows_by_focus_history = sort_windows_by_focus_history
|
||||
ctx.window_address_set = window_address_set
|
||||
ctx.window_address_list = window_address_list
|
||||
ctx.window_address_in_set = window_address_in_set
|
||||
ctx.windows_by_address = windows_by_address
|
||||
ctx.numeric_component = numeric_component
|
||||
ctx.window_center = window_center
|
||||
ctx.tiled_window_geometry = tiled_window_geometry
|
||||
ctx.window_distance_squared = window_distance_squared
|
||||
ctx.sort_windows_by_visual_position = sort_windows_by_visual_position
|
||||
ctx.grouping_direction = grouping_direction
|
||||
ctx.grouping_directions = grouping_directions
|
||||
ctx.workspace_window_count = workspace_window_count
|
||||
ctx.find_empty_workspace = find_empty_workspace
|
||||
end
|
||||
|
||||
return M
|
||||
48
dotfiles/config/hypr/hyprland/events.lua
Normal file
48
dotfiles/config/hypr/hyprland/events.lua
Normal file
@@ -0,0 +1,48 @@
|
||||
local M = {}
|
||||
|
||||
function M.setup(ctx)
|
||||
local _ENV = ctx
|
||||
hl.on("hyprland.start", function()
|
||||
apply_nstack_config()
|
||||
apply_hyprexpo_config()
|
||||
apply_hyprwinview_config()
|
||||
apply_rules()
|
||||
hl.exec_cmd("sh -lc '/run/current-system/sw/bin/uwsm finalize HYPRLAND_INSTANCE_SIGNATURE XDG_CURRENT_DESKTOP XDG_SESSION_DESKTOP XDG_SESSION_TYPE XAUTHORITY IMALISON_SESSION_TYPE=wayland IMALISON_WINDOW_MANAGER=hyprland || dbus-update-activation-environment --systemd XDG_RUNTIME_DIR WAYLAND_DISPLAY DISPLAY XAUTHORITY HYPRLAND_INSTANCE_SIGNATURE XDG_CURRENT_DESKTOP XDG_SESSION_DESKTOP XDG_SESSION_TYPE IMALISON_SESSION_TYPE IMALISON_WINDOW_MANAGER; systemctl --user start hyprland-session.target'")
|
||||
hl.exec_cmd("hypridle")
|
||||
hl.exec_cmd("wl-paste --type text --watch cliphist store")
|
||||
hl.exec_cmd("wl-paste --type image --watch cliphist store")
|
||||
write_layout_state()
|
||||
schedule_nstack_count_update()
|
||||
refresh_monitor_reserved_cache(0.25)
|
||||
refresh_monitor_reserved_cache(1.25)
|
||||
end)
|
||||
|
||||
hl.on("config.reloaded", apply_nstack_config)
|
||||
hl.on("config.reloaded", apply_hyprexpo_config)
|
||||
hl.on("config.reloaded", apply_hyprwinview_config)
|
||||
hl.on("config.reloaded", apply_rules)
|
||||
hl.on("config.reloaded", refresh_shell_workarea_and_scratchpads)
|
||||
hl.on("layer.opened", refresh_shell_workarea_and_scratchpads)
|
||||
hl.on("layer.closed", refresh_shell_workarea_and_scratchpads)
|
||||
hl.on("monitor.added", refresh_shell_workarea_and_scratchpads)
|
||||
hl.on("monitor.removed", refresh_shell_workarea_and_scratchpads)
|
||||
hl.on("monitor.layout_changed", refresh_shell_workarea_and_scratchpads)
|
||||
|
||||
hl.on("window.open", schedule_nstack_count_update)
|
||||
hl.on("window.destroy", schedule_nstack_count_update)
|
||||
hl.on("window.kill", 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("monitor.focused", sync_layout_for_active_workspace)
|
||||
|
||||
hl.on("window.open", update_monocle_notice)
|
||||
hl.on("window.destroy", update_monocle_notice)
|
||||
hl.on("window.kill", update_monocle_notice)
|
||||
hl.on("window.move_to_workspace", update_monocle_notice)
|
||||
|
||||
hl.on("window.open", adopt_matching_scratchpad_window)
|
||||
hl.on("window.class", adopt_matching_scratchpad_window)
|
||||
hl.on("window.title", adopt_matching_scratchpad_window)
|
||||
end
|
||||
|
||||
return M
|
||||
596
dotfiles/config/hypr/hyprland/layouts.lua
Normal file
596
dotfiles/config/hypr/hyprland/layouts.lua
Normal file
@@ -0,0 +1,596 @@
|
||||
local M = {}
|
||||
|
||||
function M.setup(ctx)
|
||||
local _ENV = ctx
|
||||
local function is_nstack_layout(layout)
|
||||
return layout == columns_layout or layout == grid_layout
|
||||
end
|
||||
|
||||
local function hyprland_layout(layout)
|
||||
if layout == grid_layout then
|
||||
return columns_layout
|
||||
end
|
||||
return layout
|
||||
end
|
||||
|
||||
local function update_nstack_count()
|
||||
if not enable_nstack or not is_nstack_layout(current_layout) then
|
||||
return
|
||||
end
|
||||
|
||||
local workspace = hl.get_active_workspace()
|
||||
local count = tiled_window_count(workspace)
|
||||
if count == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local stack_count = count
|
||||
if current_layout == grid_layout then
|
||||
stack_count = math.ceil(math.sqrt(count))
|
||||
end
|
||||
|
||||
stack_count = math.max(stack_count, 2)
|
||||
dispatch(hl.dsp.layout("setstackcount " .. tostring(stack_count)))
|
||||
end
|
||||
|
||||
local function schedule_nstack_count_update()
|
||||
if stack_update_timer then
|
||||
stack_update_timer:set_enabled(false)
|
||||
end
|
||||
|
||||
stack_update_timer = hl.timer(update_nstack_count, { timeout = 25, type = "oneshot" })
|
||||
end
|
||||
|
||||
local function dismiss_monocle_notice()
|
||||
if monocle_notice and monocle_notice:is_alive() then
|
||||
monocle_notice:dismiss()
|
||||
end
|
||||
monocle_notice = nil
|
||||
end
|
||||
|
||||
local function update_monocle_notice()
|
||||
if current_layout ~= monocle_layout then
|
||||
dismiss_monocle_notice()
|
||||
return
|
||||
end
|
||||
|
||||
local workspace = hl.get_active_workspace()
|
||||
local count = tiled_window_count(workspace)
|
||||
if count <= 1 then
|
||||
dismiss_monocle_notice()
|
||||
return
|
||||
end
|
||||
|
||||
local text = "Monocle: " .. tostring(count) .. " windows"
|
||||
if monocle_notice and monocle_notice:is_alive() then
|
||||
monocle_notice:set_text(text)
|
||||
monocle_notice:set_timeout(60000)
|
||||
monocle_notice:pause()
|
||||
else
|
||||
monocle_notice = hl.notification.create({
|
||||
text = text,
|
||||
duration = 60000,
|
||||
icon = notification_icons.info,
|
||||
color = "rgba(edb443ff)",
|
||||
font_size = 13,
|
||||
})
|
||||
monocle_notice:pause()
|
||||
end
|
||||
end
|
||||
|
||||
local function layout_name(layout)
|
||||
return layout_names[layout] or tostring(layout)
|
||||
end
|
||||
|
||||
local function notify_layout(layout)
|
||||
hl.notification.create({
|
||||
text = "Layout: " .. layout_name(layout),
|
||||
duration = 1200,
|
||||
icon = notification_icons.info,
|
||||
color = "rgba(edb443ff)",
|
||||
font_size = 13,
|
||||
})
|
||||
end
|
||||
|
||||
local function set_layout(layout)
|
||||
workspace_layouts[workspace_key()] = layout
|
||||
current_layout = layout
|
||||
hl.config({ general = { layout = hyprland_layout(layout) } })
|
||||
write_layout_state()
|
||||
|
||||
if is_nstack_layout(layout) then
|
||||
dismiss_monocle_notice()
|
||||
schedule_nstack_count_update()
|
||||
else
|
||||
update_monocle_notice()
|
||||
end
|
||||
end
|
||||
|
||||
_G.im_hyprland_set_layout = function(layout)
|
||||
if not layout_names[layout] then
|
||||
hl.notification.create({
|
||||
text = "Unknown layout: " .. tostring(layout),
|
||||
duration = 1800,
|
||||
icon = notification_icons.warning,
|
||||
color = "rgba(edb443ff)",
|
||||
font_size = 13,
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
set_layout(layout)
|
||||
notify_layout(layout)
|
||||
end
|
||||
|
||||
local function sync_layout_for_active_workspace()
|
||||
current_layout = current_workspace_layout()
|
||||
hl.config({ general = { layout = hyprland_layout(current_layout) } })
|
||||
write_layout_state()
|
||||
|
||||
if is_nstack_layout(current_layout) then
|
||||
dismiss_monocle_notice()
|
||||
schedule_nstack_count_update()
|
||||
else
|
||||
update_monocle_notice()
|
||||
end
|
||||
end
|
||||
|
||||
local function cycle_layout(delta)
|
||||
local current_index = 1
|
||||
for index, layout in ipairs(layout_cycle) do
|
||||
if layout == current_layout then
|
||||
current_index = index
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
local next_index = ((current_index - 1 + delta) % #layout_cycle) + 1
|
||||
local next_layout = layout_cycle[next_index]
|
||||
set_layout(next_layout)
|
||||
notify_layout(next_layout)
|
||||
end
|
||||
|
||||
local function toggle_columns_monocle()
|
||||
if current_layout == columns_layout then
|
||||
set_layout(monocle_layout)
|
||||
else
|
||||
set_layout(columns_layout)
|
||||
end
|
||||
end
|
||||
|
||||
local function active_group_size()
|
||||
local window = hl.get_active_window()
|
||||
return window and window.group and window.group.size or 0
|
||||
end
|
||||
|
||||
local function monocle_next()
|
||||
local window = hl.get_active_window()
|
||||
if window and window.group and window.group.size and window.group.size > 1 then
|
||||
dispatch(hl.dsp.group.next({ window = window_selector(window) }))
|
||||
elseif current_layout == monocle_layout then
|
||||
dispatch(hl.dsp.layout("cyclenext"))
|
||||
update_monocle_notice()
|
||||
else
|
||||
dispatch(hl.dsp.window.cycle_next({ next = true, tiled = true, floating = false }))
|
||||
end
|
||||
end
|
||||
|
||||
local function monocle_prev()
|
||||
local window = hl.get_active_window()
|
||||
if window and window.group and window.group.size and window.group.size > 1 then
|
||||
dispatch(hl.dsp.group.prev({ window = window_selector(window) }))
|
||||
elseif current_layout == monocle_layout then
|
||||
dispatch(hl.dsp.layout("cycleprev"))
|
||||
update_monocle_notice()
|
||||
else
|
||||
dispatch(hl.dsp.window.cycle_next({ next = false, tiled = true, floating = false }))
|
||||
end
|
||||
end
|
||||
|
||||
local function focus_direction(direction)
|
||||
overview_trace("focus_direction " .. direction)
|
||||
if active_group_size() > 1 or current_layout == monocle_layout then
|
||||
if direction == "up" or direction == "left" then
|
||||
monocle_prev()
|
||||
else
|
||||
monocle_next()
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
dispatch(hl.dsp.focus({ direction = direction }))
|
||||
end
|
||||
|
||||
local function swap_direction(direction)
|
||||
if enable_nstack and is_nstack_layout(current_layout) and active_group_size() <= 1 then
|
||||
dispatch(hl.dsp.layout("swapdirection " .. direction))
|
||||
return
|
||||
end
|
||||
|
||||
dispatch(hl.dsp.window.swap({ direction = direction }))
|
||||
end
|
||||
|
||||
local function focus_workspace(workspace_id)
|
||||
dispatch(hl.dsp.focus({ workspace = tostring(workspace_id), on_current_monitor = true }))
|
||||
end
|
||||
|
||||
local function move_window_to_workspace(workspace_id, follow, window)
|
||||
local target_window = window or hl.get_active_window()
|
||||
local target_selector = window_selector(target_window)
|
||||
dispatch(hl.dsp.window.move({ workspace = tostring(workspace_id), follow = false, window = target_selector }))
|
||||
if follow then
|
||||
focus_workspace(workspace_id)
|
||||
if target_selector then
|
||||
dispatch(hl.dsp.focus({ window = target_selector }))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function notify_tabbed_group(text)
|
||||
hl.notification.create({
|
||||
text = text,
|
||||
duration = 1800,
|
||||
icon = notification_icons.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
|
||||
dispatch(hl.dsp.focus({ window = selector }))
|
||||
dispatch(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 ordered_windows_for_tabbed_group_restore(state, workspace_id)
|
||||
local ordered = {}
|
||||
local seen = {}
|
||||
local live_windows = windows_by_address()
|
||||
local workspace = workspace_id and hl.get_workspace(tostring(workspace_id)) or active_workspace()
|
||||
|
||||
if state and state.order then
|
||||
for _, address in ipairs(state.order) do
|
||||
local window = live_windows[address]
|
||||
if window and not window.floating and not window.hidden and (not workspace or same_workspace(window.workspace, workspace)) then
|
||||
ordered[#ordered + 1] = window
|
||||
seen[address] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if workspace then
|
||||
for _, window in ipairs(tiled_windows(workspace)) do
|
||||
if window and window.address and not seen[window.address] then
|
||||
ordered[#ordered + 1] = window
|
||||
seen[window.address] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return ordered
|
||||
end
|
||||
|
||||
local function restore_tabbed_group_window_order(state, workspace_id)
|
||||
local ordered = ordered_windows_for_tabbed_group_restore(state, workspace_id)
|
||||
if #ordered <= 1 or not workspace_id then
|
||||
return
|
||||
end
|
||||
|
||||
local restore_workspace = tabbed_group_restore_workspace_prefix .. tostring(workspace_id)
|
||||
for _, window in ipairs(ordered) do
|
||||
move_window_to_workspace(restore_workspace, false, window)
|
||||
end
|
||||
|
||||
for _, window in ipairs(ordered) do
|
||||
move_window_to_workspace(workspace_id, false, window)
|
||||
end
|
||||
end
|
||||
|
||||
local function restore_workspace_tabbed_group()
|
||||
local key = workspace_key()
|
||||
local state = tabbed_workspace_groups[key]
|
||||
local anchor = find_tabbed_group_anchor(state)
|
||||
local anchor_selector = window_selector(anchor)
|
||||
local target_workspace_id = anchor and anchor.workspace and anchor.workspace.id
|
||||
|
||||
if not anchor_selector then
|
||||
tabbed_workspace_groups[key] = nil
|
||||
set_layout(columns_layout)
|
||||
notify_tabbed_group("No tabbed group to restore")
|
||||
return
|
||||
end
|
||||
|
||||
dispatch(hl.dsp.focus({ window = anchor_selector }))
|
||||
dispatch(hl.dsp.group.toggle({ window = anchor_selector }))
|
||||
tabbed_workspace_groups[key] = nil
|
||||
set_layout(columns_layout)
|
||||
restore_tabbed_group_window_order(state, target_workspace_id)
|
||||
dispatch(hl.dsp.focus({ window = anchor_selector }))
|
||||
schedule_nstack_count_update()
|
||||
end
|
||||
|
||||
local function gather_workspace_into_tabbed_group()
|
||||
local workspace = active_workspace()
|
||||
if not is_normal_workspace(workspace) then
|
||||
return
|
||||
end
|
||||
|
||||
local key = workspace_key(workspace)
|
||||
if tabbed_workspace_groups[key] or active_group_size() > 1 then
|
||||
restore_workspace_tabbed_group()
|
||||
return
|
||||
end
|
||||
|
||||
local original_windows = tiled_windows(workspace)
|
||||
sort_windows_by_visual_position(original_windows)
|
||||
local original_order = window_address_list(original_windows)
|
||||
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)
|
||||
|
||||
dispatch(hl.dsp.focus({ window = anchor_selector }))
|
||||
dispatch(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
|
||||
dispatch(hl.dsp.focus({ window = anchor_selector }))
|
||||
dispatch(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,
|
||||
order = original_order,
|
||||
windows = candidate_addresses,
|
||||
}
|
||||
dispatch(hl.dsp.focus({ window = anchor_selector }))
|
||||
end
|
||||
|
||||
local function force_columns_layout()
|
||||
if active_group_size() > 1 or tabbed_workspace_groups[workspace_key()] then
|
||||
restore_workspace_tabbed_group()
|
||||
else
|
||||
set_layout(columns_layout)
|
||||
end
|
||||
end
|
||||
|
||||
local function cycle_layout_or_restore_tabbed_group()
|
||||
if active_group_size() > 1 or tabbed_workspace_groups[workspace_key()] then
|
||||
restore_workspace_tabbed_group()
|
||||
return
|
||||
end
|
||||
|
||||
cycle_layout(1)
|
||||
end
|
||||
|
||||
local function copy_windows(workspace)
|
||||
local windows = {}
|
||||
if not workspace then
|
||||
return windows
|
||||
end
|
||||
|
||||
for _, window in ipairs(hl.get_workspace_windows(workspace)) do
|
||||
if window and not window.hidden then
|
||||
windows[#windows + 1] = window
|
||||
end
|
||||
end
|
||||
|
||||
return windows
|
||||
end
|
||||
|
||||
local function swap_current_workspace_with(target_id)
|
||||
local current = active_workspace()
|
||||
if not current or not current.id or current.id == target_id then
|
||||
return
|
||||
end
|
||||
|
||||
local target = hl.get_workspace(tostring(target_id))
|
||||
local current_windows = copy_windows(current)
|
||||
local target_windows = copy_windows(target)
|
||||
|
||||
for _, window in ipairs(current_windows) do
|
||||
move_window_to_workspace(target_id, false, window)
|
||||
end
|
||||
|
||||
for _, window in ipairs(target_windows) do
|
||||
move_window_to_workspace(current.id, false, window)
|
||||
end
|
||||
|
||||
focus_workspace(current.id)
|
||||
end
|
||||
|
||||
local function enter_workspace_swap_mode()
|
||||
hl.notification.create({
|
||||
text = "Swap with workspace 1-9",
|
||||
duration = 2200,
|
||||
icon = notification_icons.info,
|
||||
color = "rgba(edb443ff)",
|
||||
font_size = 13,
|
||||
})
|
||||
dispatch(hl.dsp.submap("swap-workspace"))
|
||||
end
|
||||
|
||||
local function focus_next_empty_workspace()
|
||||
local workspace_id = find_empty_workspace(hl.get_active_monitor(), active_workspace_id())
|
||||
if workspace_id then
|
||||
focus_workspace(workspace_id)
|
||||
end
|
||||
end
|
||||
|
||||
local function move_to_next_empty_workspace(follow)
|
||||
local window = hl.get_active_window()
|
||||
if not window then
|
||||
return
|
||||
end
|
||||
|
||||
local workspace_id = find_empty_workspace(hl.get_active_monitor(), active_workspace_id())
|
||||
if workspace_id then
|
||||
move_window_to_workspace(workspace_id, follow, window)
|
||||
end
|
||||
end
|
||||
|
||||
local function cycle_workspace(delta)
|
||||
local current = active_workspace_id()
|
||||
local next_workspace = ((current - 1 + delta) % max_workspace) + 1
|
||||
focus_workspace(next_workspace)
|
||||
end
|
||||
|
||||
local function move_window_to_monitor(direction, follow)
|
||||
local window = hl.get_active_window()
|
||||
if not window then
|
||||
return
|
||||
end
|
||||
|
||||
local original_monitor = hl.get_active_monitor()
|
||||
dispatch(hl.dsp.window.move({ monitor = direction, follow = follow, window = window_selector(window) }))
|
||||
|
||||
if not follow and original_monitor then
|
||||
dispatch(hl.dsp.focus({ monitor = original_monitor }))
|
||||
end
|
||||
end
|
||||
|
||||
local function move_window_to_empty_workspace_on_monitor(direction)
|
||||
local window = hl.get_active_window()
|
||||
local original_monitor = hl.get_active_monitor()
|
||||
local target_monitor = hl.get_monitor(direction)
|
||||
|
||||
if not window or not original_monitor or not target_monitor or target_monitor == original_monitor then
|
||||
return
|
||||
end
|
||||
|
||||
local workspace_id = find_empty_workspace(target_monitor, active_workspace_id())
|
||||
if not workspace_id then
|
||||
return
|
||||
end
|
||||
|
||||
dispatch(hl.dsp.focus({ monitor = target_monitor }))
|
||||
focus_workspace(workspace_id)
|
||||
dispatch(hl.dsp.focus({ monitor = original_monitor }))
|
||||
move_window_to_workspace(workspace_id, false, window)
|
||||
end
|
||||
|
||||
ctx.is_nstack_layout = is_nstack_layout
|
||||
ctx.hyprland_layout = hyprland_layout
|
||||
ctx.update_nstack_count = update_nstack_count
|
||||
ctx.schedule_nstack_count_update = schedule_nstack_count_update
|
||||
ctx.dismiss_monocle_notice = dismiss_monocle_notice
|
||||
ctx.update_monocle_notice = update_monocle_notice
|
||||
ctx.layout_name = layout_name
|
||||
ctx.notify_layout = notify_layout
|
||||
ctx.set_layout = set_layout
|
||||
ctx.sync_layout_for_active_workspace = sync_layout_for_active_workspace
|
||||
ctx.cycle_layout = cycle_layout
|
||||
ctx.toggle_columns_monocle = toggle_columns_monocle
|
||||
ctx.active_group_size = active_group_size
|
||||
ctx.monocle_next = monocle_next
|
||||
ctx.monocle_prev = monocle_prev
|
||||
ctx.focus_direction = focus_direction
|
||||
ctx.swap_direction = swap_direction
|
||||
ctx.focus_workspace = focus_workspace
|
||||
ctx.move_window_to_workspace = move_window_to_workspace
|
||||
ctx.notify_tabbed_group = notify_tabbed_group
|
||||
ctx.active_workspace_tiled_group_candidates = active_workspace_tiled_group_candidates
|
||||
ctx.move_window_into_group = move_window_into_group
|
||||
ctx.find_tabbed_group_anchor = find_tabbed_group_anchor
|
||||
ctx.ordered_windows_for_tabbed_group_restore = ordered_windows_for_tabbed_group_restore
|
||||
ctx.restore_tabbed_group_window_order = restore_tabbed_group_window_order
|
||||
ctx.restore_workspace_tabbed_group = restore_workspace_tabbed_group
|
||||
ctx.gather_workspace_into_tabbed_group = gather_workspace_into_tabbed_group
|
||||
ctx.force_columns_layout = force_columns_layout
|
||||
ctx.cycle_layout_or_restore_tabbed_group = cycle_layout_or_restore_tabbed_group
|
||||
ctx.copy_windows = copy_windows
|
||||
ctx.swap_current_workspace_with = swap_current_workspace_with
|
||||
ctx.enter_workspace_swap_mode = enter_workspace_swap_mode
|
||||
ctx.focus_next_empty_workspace = focus_next_empty_workspace
|
||||
ctx.move_to_next_empty_workspace = move_to_next_empty_workspace
|
||||
ctx.cycle_workspace = cycle_workspace
|
||||
ctx.move_window_to_monitor = move_window_to_monitor
|
||||
ctx.move_window_to_empty_workspace_on_monitor = move_window_to_empty_workspace_on_monitor
|
||||
end
|
||||
|
||||
return M
|
||||
196
dotfiles/config/hypr/hyprland/settings.lua
Normal file
196
dotfiles/config/hypr/hyprland/settings.lua
Normal file
@@ -0,0 +1,196 @@
|
||||
local M = {}
|
||||
|
||||
function M.setup(ctx)
|
||||
local _ENV = ctx
|
||||
if enable_nstack then
|
||||
hl.plugin.load("/run/current-system/sw/lib/libhyprNStack.so")
|
||||
end
|
||||
if enable_hyprexpo and not verify_config then
|
||||
hl.plugin.load("/run/current-system/sw/lib/libhyprexpo.so")
|
||||
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("HYPRCURSOR_SIZE", "24")
|
||||
hl.env("QT_QPA_PLATFORMTHEME", "qt5ct")
|
||||
hl.env("HYPR_MAX_WORKSPACE", "9")
|
||||
|
||||
hl.config({
|
||||
input = {
|
||||
kb_layout = "us",
|
||||
kb_variant = "",
|
||||
kb_model = "",
|
||||
kb_options = "",
|
||||
kb_rules = "",
|
||||
follow_mouse = 1,
|
||||
sensitivity = 0,
|
||||
touchpad = {
|
||||
natural_scroll = false,
|
||||
},
|
||||
},
|
||||
cursor = {
|
||||
persistent_warps = true,
|
||||
},
|
||||
general = {
|
||||
gaps_in = 5,
|
||||
gaps_out = 10,
|
||||
border_size = 2,
|
||||
col = {
|
||||
active_border = { colors = { "rgba(3b82f6ee)", "rgba(33ccffee)" }, angle = 45 },
|
||||
inactive_border = "rgba(00000000)",
|
||||
},
|
||||
layout = columns_layout,
|
||||
allow_tearing = false,
|
||||
},
|
||||
decoration = {
|
||||
rounding = 5,
|
||||
blur = {
|
||||
enabled = true,
|
||||
size = 3,
|
||||
passes = 1,
|
||||
},
|
||||
active_opacity = 1.0,
|
||||
inactive_opacity = 0.9,
|
||||
},
|
||||
animations = {
|
||||
enabled = true,
|
||||
},
|
||||
binds = {
|
||||
allow_workspace_cycles = true,
|
||||
workspace_back_and_forth = true,
|
||||
},
|
||||
group = {
|
||||
group_on_movetoworkspace = false,
|
||||
col = {
|
||||
border_active = "rgba(edb443ff)",
|
||||
border_inactive = "rgba(091f2eff)",
|
||||
},
|
||||
groupbar = {
|
||||
enabled = true,
|
||||
blur = true,
|
||||
font_size = 13,
|
||||
gradients = true,
|
||||
height = 26,
|
||||
indicator_gap = 0,
|
||||
indicator_height = 1,
|
||||
rounding = 5,
|
||||
gradient_rounding = 5,
|
||||
text_padding = 8,
|
||||
col = {
|
||||
active = "rgba(edb443ff)",
|
||||
inactive = "rgba(101820f2)",
|
||||
},
|
||||
text_color = "rgba(091018ff)",
|
||||
text_color_inactive = "rgba(f2f5f7ff)",
|
||||
},
|
||||
},
|
||||
misc = {
|
||||
force_default_wallpaper = 0,
|
||||
disable_hyprland_logo = true,
|
||||
exit_window_retains_fullscreen = true,
|
||||
},
|
||||
})
|
||||
|
||||
hl.curve("overshoot", { type = "bezier", points = { { 0.05, 0.9 }, { 0.1, 1.1 } } })
|
||||
hl.curve("smoothOut", { type = "bezier", points = { { 0.36, 1 }, { 0.3, 1 } } })
|
||||
hl.curve("smoothInOut", { type = "bezier", points = { { 0.42, 0 }, { 0.58, 1 } } })
|
||||
hl.curve("linear", { type = "bezier", points = { { 0, 0 }, { 1, 1 } } })
|
||||
|
||||
local animations = {
|
||||
{ leaf = "global", enabled = true, speed = 8, bezier = "default" },
|
||||
|
||||
{ leaf = "windows", enabled = true, speed = 6, bezier = "overshoot", style = "gnomed" },
|
||||
{ leaf = "windowsIn", enabled = true, speed = 6, bezier = "overshoot", style = "gnomed" },
|
||||
{ leaf = "windowsOut", enabled = true, speed = 5, bezier = "smoothInOut", style = "gnomed" },
|
||||
{ leaf = "windowsMove", enabled = true, speed = 6, bezier = "smoothOut" },
|
||||
|
||||
{ leaf = "border", enabled = false },
|
||||
{ leaf = "borderangle", enabled = false },
|
||||
|
||||
{ leaf = "fade", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||
{ leaf = "fadeIn", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||
{ leaf = "fadeOut", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||
{ leaf = "fadeSwitch", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||
{ leaf = "fadeShadow", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||
{ leaf = "fadeGlow", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||
{ leaf = "fadeDim", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||
{ leaf = "fadeLayers", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||
{ leaf = "fadeLayersIn", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||
{ leaf = "fadeLayersOut", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||
{ leaf = "fadePopups", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||
{ leaf = "fadePopupsIn", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||
{ leaf = "fadePopupsOut", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||
{ leaf = "fadeDpms", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||
|
||||
{ leaf = "layers", enabled = true, speed = 5, bezier = "smoothOut", style = "fade" },
|
||||
{ leaf = "layersIn", enabled = true, speed = 5, bezier = "smoothOut", style = "fade" },
|
||||
{ leaf = "layersOut", enabled = true, speed = 5, bezier = "smoothOut", style = "fade" },
|
||||
|
||||
{ leaf = "workspaces", enabled = true, speed = 6, bezier = "smoothOut", style = "slidefade 15%" },
|
||||
{ leaf = "workspacesIn", enabled = true, speed = 6, bezier = "smoothOut", style = "slidefade 15%" },
|
||||
{ leaf = "workspacesOut", enabled = true, speed = 6, bezier = "smoothOut", style = "slidefade 15%" },
|
||||
{ leaf = "specialWorkspace", enabled = true, speed = 6, bezier = "smoothOut", style = "slidevert" },
|
||||
{ leaf = "specialWorkspaceIn", enabled = true, speed = 6, bezier = "smoothOut", style = "slidevert" },
|
||||
{ leaf = "specialWorkspaceOut", enabled = true, speed = 6, bezier = "smoothOut", style = "slidevert" },
|
||||
|
||||
{ leaf = "zoomFactor", enabled = true, speed = 7, bezier = "smoothOut" },
|
||||
-- Disabled for now: Hyprland 0.54.0 can crash while damaging a monitor
|
||||
-- from this startup animation's update callback during output discovery.
|
||||
-- { leaf = "monitorAdded", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||
{ leaf = "monitorAdded", enabled = false, speed = 5, bezier = "smoothOut" },
|
||||
}
|
||||
|
||||
for _, animation in ipairs(animations) do
|
||||
hl.animation(animation)
|
||||
end
|
||||
|
||||
local function apply_rules()
|
||||
if verify_config then
|
||||
return
|
||||
end
|
||||
|
||||
hl.workspace_rule({ workspace = "w[tv1]s[false]", gaps_out = 0, gaps_in = 0 })
|
||||
hl.workspace_rule({ workspace = "f[1]s[false]", gaps_out = 0, gaps_in = 0 })
|
||||
|
||||
hl.window_rule({ match = { class = "^()$", title = "^()$" }, float = true })
|
||||
hl.window_rule({ match = { title = "^(Picture-in-Picture)$" }, 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 = "^(Confirm)$" }, float = true })
|
||||
hl.window_rule({
|
||||
match = { class = "^(com\\.mitchellh\\.ghostty\\.dropdown)$" },
|
||||
animation = "slide top",
|
||||
})
|
||||
hl.window_rule({
|
||||
match = { class = "^(.*[Rr]umno.*)$" },
|
||||
float = true,
|
||||
pin = true,
|
||||
center = true,
|
||||
decorate = false,
|
||||
no_shadow = true,
|
||||
})
|
||||
hl.window_rule({
|
||||
match = { title = "^(.*[Rr]umno.*)$" },
|
||||
float = true,
|
||||
pin = true,
|
||||
center = true,
|
||||
decorate = false,
|
||||
no_shadow = true,
|
||||
})
|
||||
hl.window_rule({
|
||||
name = "subtle-pinned-window-border",
|
||||
match = { pin = true },
|
||||
border_size = 2,
|
||||
border_color = "rgba(edb443ff) rgba(ff4d5dcc)",
|
||||
})
|
||||
end
|
||||
|
||||
ctx.apply_rules = apply_rules
|
||||
end
|
||||
|
||||
return M
|
||||
105
dotfiles/config/hypr/hyprland/state.lua
Normal file
105
dotfiles/config/hypr/hyprland/state.lua
Normal file
@@ -0,0 +1,105 @@
|
||||
local shell_ui_command = "hypr_shell_ui"
|
||||
local columns_layout = "nStack"
|
||||
local large_main_layout = "master"
|
||||
local grid_layout = "grid"
|
||||
local monocle_layout = "monocle"
|
||||
|
||||
return {
|
||||
main_mod = "SUPER",
|
||||
mod_alt = "SUPER + ALT",
|
||||
hyper = "SUPER + CTRL + ALT",
|
||||
|
||||
terminal = "ghostty --gtk-single-instance=false",
|
||||
shell_ui_command = shell_ui_command,
|
||||
launcher_command = shell_ui_command .. " launcher",
|
||||
run_menu = shell_ui_command .. " run",
|
||||
|
||||
-- Hyprland shadows ordinary keybinds after one fires; without transparent,
|
||||
-- the first overview chord after a focus-moving bind can be skipped.
|
||||
overview_bind_opts = { dont_inhibit = true, transparent = true },
|
||||
overview_trace_enabled_path = "/tmp/hypr-overview-bind.enable",
|
||||
overview_trace_path = "/tmp/hypr-overview-bind.log",
|
||||
notification_icons = {
|
||||
warning = 0,
|
||||
info = 1,
|
||||
hint = 2,
|
||||
error = 3,
|
||||
confused = 4,
|
||||
ok = 5,
|
||||
none = 6,
|
||||
},
|
||||
|
||||
max_workspace = 9,
|
||||
scratchpad_size_ratio = 0.95,
|
||||
dropdown_height_ratio = 0.5,
|
||||
columns_layout = columns_layout,
|
||||
large_main_layout = large_main_layout,
|
||||
grid_layout = grid_layout,
|
||||
monocle_layout = monocle_layout,
|
||||
layout_cycle = { columns_layout, large_main_layout, grid_layout },
|
||||
layout_names = {
|
||||
[columns_layout] = "Columns",
|
||||
[large_main_layout] = "Large main",
|
||||
[grid_layout] = "Grid",
|
||||
[monocle_layout] = "Monocle",
|
||||
},
|
||||
minimized_workspace = "special:minimized",
|
||||
tabbed_group_restore_workspace_prefix = "special:tabbed-monocle-restore-",
|
||||
current_layout = columns_layout,
|
||||
enable_nstack = true,
|
||||
enable_hyprexpo = true,
|
||||
enable_hyprwinview = true,
|
||||
enable_workspace_history = true,
|
||||
configure_nstack_plugin_from_lua = false,
|
||||
workspace_layouts = {},
|
||||
minimized_windows = {},
|
||||
tabbed_workspace_groups = {},
|
||||
window_picker_mode = nil,
|
||||
window_picker_candidates = {},
|
||||
stack_update_timer = nil,
|
||||
monocle_notice = nil,
|
||||
scratchpad_pending = {},
|
||||
monitor_reserved_cache_path = (os.getenv("XDG_RUNTIME_DIR") or "/tmp") .. "/hyprland-monitor-reserved.tsv",
|
||||
scratchpad_fallback_reserved_top = 60,
|
||||
|
||||
scratchpads = {
|
||||
codex = {
|
||||
command = "codex-desktop",
|
||||
class = "codex-desktop",
|
||||
},
|
||||
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",
|
||||
classes = { "Element", "electron" },
|
||||
title = "Element",
|
||||
},
|
||||
slack = {
|
||||
command = "slack",
|
||||
class = "Slack",
|
||||
},
|
||||
messages = {
|
||||
command = "google-chrome-stable --profile-directory=Default --app=https://messages.google.com/web/conversations",
|
||||
class = "chrome-messages.google.com",
|
||||
},
|
||||
transmission = {
|
||||
command = "transmission-gtk",
|
||||
class = "transmission-gtk",
|
||||
},
|
||||
dropdown = {
|
||||
command = "ghostty --config-file=/home/imalison/.config/ghostty/dropdown",
|
||||
class = "com.mitchellh.ghostty.dropdown",
|
||||
dropdown = true,
|
||||
},
|
||||
},
|
||||
}
|
||||
801
dotfiles/config/hypr/hyprland/windows.lua
Normal file
801
dotfiles/config/hypr/hyprland/windows.lua
Normal file
@@ -0,0 +1,801 @@
|
||||
local M = {}
|
||||
|
||||
function M.setup(ctx)
|
||||
local _ENV = ctx
|
||||
local function same_class_windows(class_name)
|
||||
local windows = {}
|
||||
if not class_name or class_name == "" then
|
||||
return windows
|
||||
end
|
||||
|
||||
for _, window in ipairs(hl.get_windows()) do
|
||||
if is_normal_window(window) and window.class == class_name then
|
||||
windows[#windows + 1] = window
|
||||
end
|
||||
end
|
||||
|
||||
return windows
|
||||
end
|
||||
|
||||
local function short_text(value, limit)
|
||||
value = tostring(value or "")
|
||||
value = value:gsub("[%c\t\r\n]", " ")
|
||||
if #value <= limit then
|
||||
return value
|
||||
end
|
||||
return value:sub(1, limit - 3) .. "..."
|
||||
end
|
||||
|
||||
local function normal_windows()
|
||||
local windows = {}
|
||||
for _, window in ipairs(hl.get_windows()) do
|
||||
if is_normal_window(window) then
|
||||
windows[#windows + 1] = window
|
||||
end
|
||||
end
|
||||
|
||||
table.sort(windows, function(left, right)
|
||||
local left_workspace = left.workspace and left.workspace.id or max_workspace + 1
|
||||
local right_workspace = right.workspace and right.workspace.id or max_workspace + 1
|
||||
if left_workspace ~= right_workspace then
|
||||
return left_workspace < right_workspace
|
||||
end
|
||||
return (left.focus_history_id or 0) < (right.focus_history_id or 0)
|
||||
end)
|
||||
|
||||
return windows
|
||||
end
|
||||
|
||||
local function window_picker_entry(index, window)
|
||||
local workspace = window.workspace and window.workspace.id or "?"
|
||||
local class = short_text(window.class, 18)
|
||||
local title = short_text(window.title, 48)
|
||||
return tostring(index) .. " [" .. tostring(workspace) .. "] " .. class .. " " .. title
|
||||
end
|
||||
|
||||
local function remove_minimized_window(target)
|
||||
local remaining = {}
|
||||
local target_address = target and target.address
|
||||
for _, window in ipairs(minimized_windows) do
|
||||
if window and window.address ~= target_address then
|
||||
remaining[#remaining + 1] = window
|
||||
end
|
||||
end
|
||||
minimized_windows = remaining
|
||||
end
|
||||
|
||||
local function add_minimized_window(window)
|
||||
if not window or not window.address then
|
||||
return
|
||||
end
|
||||
|
||||
remove_minimized_window(window)
|
||||
minimized_windows[#minimized_windows + 1] = window
|
||||
end
|
||||
|
||||
local function hydrate_minimized_windows()
|
||||
local by_address = {}
|
||||
local current_by_address = {}
|
||||
local hydrated = {}
|
||||
|
||||
for _, window in ipairs(hl.get_windows()) do
|
||||
if window and window.address then
|
||||
current_by_address[window.address] = window
|
||||
end
|
||||
end
|
||||
|
||||
for _, window in ipairs(minimized_windows) do
|
||||
local current = window and window.address and current_by_address[window.address]
|
||||
if current and is_minimized_window(current) and not by_address[current.address] then
|
||||
by_address[current.address] = true
|
||||
hydrated[#hydrated + 1] = current
|
||||
end
|
||||
end
|
||||
|
||||
for _, window in pairs(current_by_address) do
|
||||
if window and window.address and is_minimized_window(window) and not by_address[window.address] then
|
||||
by_address[window.address] = true
|
||||
hydrated[#hydrated + 1] = window
|
||||
end
|
||||
end
|
||||
|
||||
minimized_windows = hydrated
|
||||
end
|
||||
|
||||
local function window_workspace_name(window)
|
||||
return window and window.workspace and window.workspace.name or ""
|
||||
end
|
||||
|
||||
local function scratchpad_workspace(name)
|
||||
return "special:scratch-" .. name
|
||||
end
|
||||
|
||||
local function as_number(value, default)
|
||||
local number = tonumber(value)
|
||||
if number == nil then
|
||||
return default
|
||||
end
|
||||
return number
|
||||
end
|
||||
|
||||
local function logical_monitor_dimension(value, scale)
|
||||
value = as_number(value, 0)
|
||||
scale = as_number(scale, 1)
|
||||
if scale <= 0 then
|
||||
scale = 1
|
||||
end
|
||||
return math.floor((value / scale) + 0.5)
|
||||
end
|
||||
|
||||
local function split_tsv(line)
|
||||
local fields = {}
|
||||
for field in (line .. "\t"):gmatch("([^\t]*)\t") do
|
||||
fields[#fields + 1] = field
|
||||
end
|
||||
return fields
|
||||
end
|
||||
|
||||
local function monitor_from_reserved_fields(monitor, fields)
|
||||
if not monitor or not monitor.name or fields[1] ~= monitor.name or #fields < 10 then
|
||||
return nil
|
||||
end
|
||||
|
||||
return {
|
||||
name = monitor.name,
|
||||
x = tonumber(fields[2]),
|
||||
y = tonumber(fields[3]),
|
||||
width = tonumber(fields[4]),
|
||||
height = tonumber(fields[5]),
|
||||
scale = tonumber(fields[6]),
|
||||
reserved = {
|
||||
tonumber(fields[7]),
|
||||
tonumber(fields[8]),
|
||||
tonumber(fields[9]),
|
||||
tonumber(fields[10]),
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
local function monitor_from_reserved_lines(monitor, lines)
|
||||
if not monitor or not monitor.name then
|
||||
return nil
|
||||
end
|
||||
|
||||
for line in lines do
|
||||
local cached = monitor_from_reserved_fields(monitor, split_tsv(line))
|
||||
if cached then
|
||||
return cached
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function monitor_from_reserved_cache(monitor)
|
||||
if verify_config or not monitor or not monitor.name then
|
||||
return nil
|
||||
end
|
||||
|
||||
local file = io.open(monitor_reserved_cache_path, "r")
|
||||
if not file then
|
||||
return nil
|
||||
end
|
||||
|
||||
local cached = monitor_from_reserved_lines(monitor, file:lines())
|
||||
file:close()
|
||||
return cached
|
||||
end
|
||||
|
||||
local function refresh_monitor_reserved_cache(delay)
|
||||
if verify_config then
|
||||
return
|
||||
end
|
||||
|
||||
local command = string.format(
|
||||
[=[sleep %.2f; cache="${XDG_RUNTIME_DIR:-/tmp}/hyprland-monitor-reserved.tsv"; tmp="$cache.tmp"; /run/current-system/sw/bin/hyprctl -j monitors 2>/dev/null | /run/current-system/sw/bin/jq -r '.[] | [.name, .x, .y, .width, .height, .scale, .reserved[0], .reserved[1], .reserved[2], .reserved[3]] | @tsv' > "$tmp" && mv "$tmp" "$cache"]=],
|
||||
as_number(delay, 0)
|
||||
)
|
||||
hl.exec_cmd("sh -lc " .. shell_quote(command))
|
||||
end
|
||||
|
||||
local function monitor_workarea(monitor)
|
||||
monitor = monitor_from_reserved_cache(monitor) or monitor
|
||||
local width = logical_monitor_dimension(monitor.width, monitor.scale)
|
||||
local height = logical_monitor_dimension(monitor.height, monitor.scale)
|
||||
local reserved = monitor.reserved or { 0, scratchpad_fallback_reserved_top, 0, 0 }
|
||||
local left = math.floor(as_number(reserved[1], 0))
|
||||
local top = math.floor(as_number(reserved[2], 0))
|
||||
local right = math.floor(as_number(reserved[3], 0))
|
||||
local bottom = math.floor(as_number(reserved[4], 0))
|
||||
local work_width = width - left - right
|
||||
local work_height = height - top - bottom
|
||||
|
||||
if work_width <= 0 then
|
||||
left = 0
|
||||
right = 0
|
||||
work_width = width
|
||||
end
|
||||
if work_height <= 0 then
|
||||
top = 0
|
||||
bottom = 0
|
||||
work_height = height
|
||||
end
|
||||
|
||||
return {
|
||||
x = math.floor(as_number(monitor.x, 0)) + left,
|
||||
y = math.floor(as_number(monitor.y, 0)) + top,
|
||||
width = work_width,
|
||||
height = work_height,
|
||||
}
|
||||
end
|
||||
|
||||
local function matching_scratchpad_windows(name)
|
||||
local def = scratchpads[name]
|
||||
local windows = {}
|
||||
if not def then
|
||||
return windows
|
||||
end
|
||||
|
||||
for _, window in ipairs(hl.get_windows()) do
|
||||
if scratchpad_window_matches(window, def) then
|
||||
windows[#windows + 1] = window
|
||||
end
|
||||
end
|
||||
|
||||
return windows
|
||||
end
|
||||
|
||||
local function apply_scratchpad_geometry(name, window, target_monitor)
|
||||
local def = scratchpads[name]
|
||||
local monitor = target_monitor or hl.get_active_monitor()
|
||||
if not def or not window or not monitor then
|
||||
return
|
||||
end
|
||||
|
||||
local workarea = monitor_workarea(monitor)
|
||||
local width
|
||||
local height
|
||||
local x
|
||||
local y
|
||||
if def.dropdown then
|
||||
width = workarea.width
|
||||
height = math.floor(workarea.height * dropdown_height_ratio)
|
||||
x = workarea.x
|
||||
y = workarea.y
|
||||
else
|
||||
width = math.floor(workarea.width * scratchpad_size_ratio)
|
||||
height = math.floor(workarea.height * scratchpad_size_ratio)
|
||||
x = workarea.x + math.floor((workarea.width - width) / 2)
|
||||
y = workarea.y + math.floor((workarea.height - height) / 2)
|
||||
end
|
||||
local selector = window_selector(window)
|
||||
|
||||
dispatch(hl.dsp.window.float({ action = "enable", window = selector }))
|
||||
dispatch(hl.dsp.window.tag({ tag = "+scratchpad", window = selector }))
|
||||
dispatch(hl.dsp.window.tag({ tag = "+scratchpad-" .. name, window = selector }))
|
||||
dispatch(hl.dsp.window.resize({ x = width, y = height, relative = false, window = selector }))
|
||||
dispatch(hl.dsp.window.move({ x = x, y = y, relative = false, window = selector }))
|
||||
if def.dropdown then
|
||||
dispatch(hl.dsp.window.set_prop({ prop = "border_size", value = "0", window = selector }))
|
||||
dispatch(hl.dsp.window.set_prop({ prop = "no_shadow", value = "1", window = selector }))
|
||||
end
|
||||
end
|
||||
|
||||
local function float_active_window_preserving_tiled_geometry()
|
||||
local geometry = tiled_window_geometry(hl.get_active_window())
|
||||
dispatch(hl.dsp.window.float({ action = "enable", window = geometry and geometry.selector or nil }))
|
||||
if geometry then
|
||||
dispatch(hl.dsp.window.resize({ x = geometry.width, y = geometry.height, relative = false, window = geometry.selector }))
|
||||
dispatch(hl.dsp.window.move({ x = geometry.x, y = geometry.y, relative = false, window = geometry.selector }))
|
||||
end
|
||||
return geometry
|
||||
end
|
||||
|
||||
local function float_and_drag_active_window()
|
||||
float_active_window_preserving_tiled_geometry()
|
||||
dispatch(hl.dsp.window.drag())
|
||||
end
|
||||
|
||||
local function float_and_resize_active_window()
|
||||
float_active_window_preserving_tiled_geometry()
|
||||
dispatch(hl.dsp.window.resize())
|
||||
end
|
||||
|
||||
local function toggle_pinned_active_window()
|
||||
local window = hl.get_active_window()
|
||||
local selector = window_selector(window)
|
||||
if not window or not selector then
|
||||
return
|
||||
end
|
||||
|
||||
if window.pinned then
|
||||
dispatch(hl.dsp.window.pin({ action = "disable", window = selector }))
|
||||
dispatch(hl.dsp.window.float({ action = "disable", window = selector }))
|
||||
return
|
||||
end
|
||||
|
||||
if not window.floating then
|
||||
float_active_window_preserving_tiled_geometry()
|
||||
end
|
||||
dispatch(hl.dsp.window.pin({ action = "enable", window = selector }))
|
||||
end
|
||||
|
||||
local function schedule_scratchpad_geometry(name, window, target_monitor)
|
||||
hl.timer(function()
|
||||
apply_scratchpad_geometry(name, window, target_monitor)
|
||||
end, { timeout = 50, type = "oneshot" })
|
||||
end
|
||||
|
||||
local function hide_scratchpad_window(name, window)
|
||||
remove_minimized_window(window)
|
||||
move_window_to_workspace(scratchpad_workspace(name), false, window)
|
||||
end
|
||||
|
||||
local function show_scratchpad_window(name, window, workspace, target_monitor)
|
||||
workspace = workspace or active_workspace()
|
||||
if not workspace then
|
||||
return
|
||||
end
|
||||
|
||||
remove_minimized_window(window)
|
||||
move_window_to_workspace(workspace.id, false, window)
|
||||
dispatch(hl.dsp.focus({ window = window_selector(window) }))
|
||||
schedule_scratchpad_geometry(name, window, target_monitor or hl.get_active_monitor())
|
||||
end
|
||||
|
||||
local function scratchpad_is_visible(window)
|
||||
local workspace = active_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
|
||||
|
||||
local function refresh_active_scratchpad_geometries()
|
||||
local monitor = hl.get_active_monitor()
|
||||
for _, active in ipairs(active_scratchpad_windows()) do
|
||||
schedule_scratchpad_geometry(active.name, active.window, monitor)
|
||||
end
|
||||
end
|
||||
|
||||
local function refresh_active_scratchpad_geometries_later(timeout)
|
||||
hl.timer(refresh_active_scratchpad_geometries, { timeout = timeout or 300, type = "oneshot" })
|
||||
end
|
||||
|
||||
local function refresh_shell_workarea_and_scratchpads()
|
||||
refresh_monitor_reserved_cache(0.15)
|
||||
refresh_active_scratchpad_geometries_later(400)
|
||||
end
|
||||
|
||||
local function adopt_matching_scratchpad_window(window)
|
||||
if not window then
|
||||
return
|
||||
end
|
||||
|
||||
for name, def in pairs(scratchpads) do
|
||||
if scratchpad_window_matches(window, def) then
|
||||
if scratchpad_pending[name] then
|
||||
local pending = scratchpad_pending[name]
|
||||
scratchpad_pending[name] = nil
|
||||
show_scratchpad_window(name, window, pending.workspace or active_workspace(), pending.monitor or hl.get_active_monitor())
|
||||
elseif scratchpad_is_visible(window) then
|
||||
schedule_scratchpad_geometry(name, window, hl.get_active_monitor())
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function current_minimized_windows()
|
||||
hydrate_minimized_windows()
|
||||
|
||||
local windows = {}
|
||||
for _, window in ipairs(minimized_windows) do
|
||||
if window and window.address and is_minimized_window(window) then
|
||||
windows[#windows + 1] = window
|
||||
end
|
||||
end
|
||||
minimized_windows = windows
|
||||
return windows
|
||||
end
|
||||
|
||||
local function restore_minimized_window(window, workspace)
|
||||
if not window or not workspace then
|
||||
return false
|
||||
end
|
||||
|
||||
move_window_to_workspace(workspace.id, false, window)
|
||||
return true
|
||||
end
|
||||
|
||||
local function window_picker_candidates_for(mode)
|
||||
if mode == "minimized" then
|
||||
return current_minimized_windows()
|
||||
end
|
||||
|
||||
local focused = hl.get_active_window()
|
||||
local workspace = active_workspace()
|
||||
local candidates = {}
|
||||
|
||||
for _, window in ipairs(normal_windows()) do
|
||||
local include = true
|
||||
if mode == "bring" and workspace and window.workspace == workspace then
|
||||
include = false
|
||||
elseif mode == "replace" and focused and window == focused then
|
||||
include = false
|
||||
end
|
||||
|
||||
if include then
|
||||
candidates[#candidates + 1] = window
|
||||
end
|
||||
end
|
||||
|
||||
return candidates
|
||||
end
|
||||
|
||||
local function activate_window_picker_candidate(index)
|
||||
local window = window_picker_candidates[index]
|
||||
local mode = window_picker_mode
|
||||
window_picker_mode = nil
|
||||
window_picker_candidates = {}
|
||||
dispatch(hl.dsp.submap("reset"))
|
||||
|
||||
if not window then
|
||||
return
|
||||
end
|
||||
|
||||
if mode == "go" then
|
||||
dispatch(hl.dsp.focus({ window = window_selector(window) }))
|
||||
return
|
||||
end
|
||||
|
||||
local workspace = active_workspace()
|
||||
if mode == "bring" and workspace then
|
||||
move_window_to_workspace(workspace.id, false, window)
|
||||
dispatch(hl.dsp.focus({ window = window_selector(window) }))
|
||||
return
|
||||
end
|
||||
|
||||
if mode == "minimized" and workspace then
|
||||
remove_minimized_window(window)
|
||||
restore_minimized_window(window, workspace)
|
||||
dispatch(hl.dsp.focus({ window = window_selector(window) }))
|
||||
return
|
||||
end
|
||||
|
||||
if mode == "replace" then
|
||||
local focused = hl.get_active_window()
|
||||
if focused and focused ~= window then
|
||||
dispatch(hl.dsp.window.swap({ target = window_selector(window), window = window_selector(focused) }))
|
||||
dispatch(hl.dsp.focus({ window = window_selector(window) }))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function enter_window_picker(mode)
|
||||
window_picker_mode = mode
|
||||
window_picker_candidates = window_picker_candidates_for(mode)
|
||||
|
||||
if #window_picker_candidates == 0 then
|
||||
local empty_text = "No windows available"
|
||||
if mode == "minimized" then
|
||||
empty_text = "No minimized windows"
|
||||
end
|
||||
|
||||
hl.notification.create({
|
||||
text = empty_text,
|
||||
duration = 1800,
|
||||
icon = notification_icons.info,
|
||||
color = "rgba(edb443ff)",
|
||||
font_size = 13,
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
local lines = {}
|
||||
local count = math.min(#window_picker_candidates, 9)
|
||||
for i = 1, count do
|
||||
lines[#lines + 1] = window_picker_entry(i, window_picker_candidates[i])
|
||||
end
|
||||
|
||||
hl.notification.create({
|
||||
text = table.concat(lines, "\n"),
|
||||
duration = 5000,
|
||||
icon = notification_icons.info,
|
||||
color = "rgba(edb443ff)",
|
||||
font_size = 11,
|
||||
})
|
||||
dispatch(hl.dsp.submap("window-picker"))
|
||||
end
|
||||
|
||||
local function gather_focused_class()
|
||||
local focused = hl.get_active_window()
|
||||
local workspace = active_workspace()
|
||||
if not focused or not workspace or not focused.class or focused.class == "" then
|
||||
return
|
||||
end
|
||||
|
||||
local count = 0
|
||||
for _, window in ipairs(same_class_windows(focused.class)) do
|
||||
if window ~= focused and window.workspace ~= workspace then
|
||||
move_window_to_workspace(workspace.id, false, window)
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
|
||||
hl.notification.create({
|
||||
text = "Gathered " .. tostring(count) .. " " .. focused.class .. " windows",
|
||||
duration = 1600,
|
||||
icon = notification_icons.info,
|
||||
color = "rgba(edb443ff)",
|
||||
font_size = 13,
|
||||
})
|
||||
end
|
||||
|
||||
local function focus_next_class()
|
||||
local focused = hl.get_active_window()
|
||||
if not focused or not focused.class or focused.class == "" then
|
||||
dispatch(hl.dsp.window.cycle_next({ next = true, tiled = true, floating = false }))
|
||||
return
|
||||
end
|
||||
|
||||
local classes = {}
|
||||
local first_by_class = {}
|
||||
for _, window in ipairs(hl.get_windows()) do
|
||||
if is_normal_window(window) and window.class and window.class ~= "" and not first_by_class[window.class] then
|
||||
first_by_class[window.class] = window
|
||||
classes[#classes + 1] = window.class
|
||||
end
|
||||
end
|
||||
|
||||
table.sort(classes)
|
||||
if #classes <= 1 then
|
||||
return
|
||||
end
|
||||
|
||||
local current_index = 1
|
||||
for index, class_name in ipairs(classes) do
|
||||
if class_name == focused.class then
|
||||
current_index = index
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
local next_class = classes[(current_index % #classes) + 1]
|
||||
local target = first_by_class[next_class]
|
||||
if target then
|
||||
dispatch(hl.dsp.focus({ window = window_selector(target) }))
|
||||
end
|
||||
end
|
||||
|
||||
local function show_active_window_info()
|
||||
local window = hl.get_active_window()
|
||||
if not window then
|
||||
hl.notification.create({
|
||||
text = "No active window",
|
||||
duration = 1800,
|
||||
icon = notification_icons.info,
|
||||
color = "rgba(edb443ff)",
|
||||
font_size = 13,
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
local workspace = window.workspace and (window.workspace.name or window.workspace.id) or "?"
|
||||
local lines = {
|
||||
"Class: " .. tostring(window.class or ""),
|
||||
"Title: " .. tostring(window.title or ""),
|
||||
"Workspace: " .. tostring(workspace),
|
||||
"Pinned: " .. tostring(window.pinned or false),
|
||||
"Address: " .. tostring(window.address or ""),
|
||||
"PID: " .. tostring(window.pid or ""),
|
||||
}
|
||||
|
||||
hl.notification.create({
|
||||
text = table.concat(lines, "\n"),
|
||||
duration = 5000,
|
||||
icon = notification_icons.info,
|
||||
color = "rgba(edb443ff)",
|
||||
font_size = 11,
|
||||
})
|
||||
end
|
||||
|
||||
local function raise_or_spawn(class_fragment, command)
|
||||
local fragment = string.lower(class_fragment)
|
||||
for _, window in ipairs(hl.get_windows()) do
|
||||
if is_normal_window(window) and window.class and string.find(string.lower(window.class), fragment, 1, true) then
|
||||
dispatch(hl.dsp.focus({ window = window_selector(window) }))
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
hl.exec_cmd(command)
|
||||
end
|
||||
|
||||
local function minimize_active_window()
|
||||
local window = hl.get_active_window()
|
||||
if not window then
|
||||
return
|
||||
end
|
||||
|
||||
add_minimized_window(window)
|
||||
move_window_to_workspace(minimized_workspace, false, window)
|
||||
end
|
||||
|
||||
local function restore_last_minimized()
|
||||
local workspace = active_workspace()
|
||||
if not workspace then
|
||||
return
|
||||
end
|
||||
|
||||
hydrate_minimized_windows()
|
||||
|
||||
while #minimized_windows > 0 do
|
||||
local window = table.remove(minimized_windows)
|
||||
if window and window.address and is_minimized_window(window) then
|
||||
restore_minimized_window(window, workspace)
|
||||
dispatch(hl.dsp.focus({ window = window_selector(window) }))
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function restore_all_minimized()
|
||||
local workspace = active_workspace()
|
||||
if not workspace then
|
||||
return
|
||||
end
|
||||
|
||||
hydrate_minimized_windows()
|
||||
|
||||
while #minimized_windows > 0 do
|
||||
restore_minimized_window(table.remove(minimized_windows), workspace)
|
||||
end
|
||||
end
|
||||
|
||||
local function minimize_other_classes()
|
||||
local focused = hl.get_active_window()
|
||||
local workspace = active_workspace()
|
||||
if not focused or not workspace then
|
||||
return
|
||||
end
|
||||
|
||||
for _, window in ipairs(tiled_windows(workspace)) do
|
||||
if window ~= focused and window.class ~= focused.class then
|
||||
add_minimized_window(window)
|
||||
move_window_to_workspace(minimized_workspace, false, window)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function restore_focused_class()
|
||||
local focused = hl.get_active_window()
|
||||
local workspace = active_workspace()
|
||||
if not focused or not workspace or not focused.class then
|
||||
return
|
||||
end
|
||||
|
||||
hydrate_minimized_windows()
|
||||
|
||||
local remaining = {}
|
||||
for _, window in ipairs(minimized_windows) do
|
||||
if window and window.class == focused.class and is_minimized_window(window) then
|
||||
restore_minimized_window(window, workspace)
|
||||
else
|
||||
remaining[#remaining + 1] = window
|
||||
end
|
||||
end
|
||||
minimized_windows = remaining
|
||||
end
|
||||
|
||||
local function toggle_scratchpad(name)
|
||||
local def = scratchpads[name]
|
||||
if not def then
|
||||
return
|
||||
end
|
||||
|
||||
if current_layout == monocle_layout then
|
||||
set_layout(columns_layout)
|
||||
end
|
||||
|
||||
local windows = matching_scratchpad_windows(name)
|
||||
if #windows == 0 then
|
||||
hide_active_scratchpads(name)
|
||||
scratchpad_pending[name] = {
|
||||
monitor = hl.get_active_monitor(),
|
||||
workspace = active_workspace(),
|
||||
}
|
||||
hl.exec_cmd(def.command)
|
||||
return
|
||||
end
|
||||
|
||||
local any_visible = false
|
||||
for _, window in ipairs(windows) do
|
||||
if scratchpad_is_visible(window) then
|
||||
any_visible = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if any_visible then
|
||||
for _, window in ipairs(windows) do
|
||||
hide_scratchpad_window(name, window)
|
||||
end
|
||||
else
|
||||
hide_active_scratchpads(name)
|
||||
local workspace = active_workspace()
|
||||
local target_monitor = hl.get_active_monitor()
|
||||
for _, window in ipairs(windows) do
|
||||
show_scratchpad_window(name, window, workspace, target_monitor)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ctx.same_class_windows = same_class_windows
|
||||
ctx.short_text = short_text
|
||||
ctx.normal_windows = normal_windows
|
||||
ctx.window_picker_entry = window_picker_entry
|
||||
ctx.remove_minimized_window = remove_minimized_window
|
||||
ctx.add_minimized_window = add_minimized_window
|
||||
ctx.hydrate_minimized_windows = hydrate_minimized_windows
|
||||
ctx.window_workspace_name = window_workspace_name
|
||||
ctx.scratchpad_workspace = scratchpad_workspace
|
||||
ctx.as_number = as_number
|
||||
ctx.logical_monitor_dimension = logical_monitor_dimension
|
||||
ctx.split_tsv = split_tsv
|
||||
ctx.monitor_from_reserved_fields = monitor_from_reserved_fields
|
||||
ctx.monitor_from_reserved_lines = monitor_from_reserved_lines
|
||||
ctx.monitor_from_reserved_cache = monitor_from_reserved_cache
|
||||
ctx.refresh_monitor_reserved_cache = refresh_monitor_reserved_cache
|
||||
ctx.monitor_workarea = monitor_workarea
|
||||
ctx.matching_scratchpad_windows = matching_scratchpad_windows
|
||||
ctx.apply_scratchpad_geometry = apply_scratchpad_geometry
|
||||
ctx.float_active_window_preserving_tiled_geometry = float_active_window_preserving_tiled_geometry
|
||||
ctx.float_and_drag_active_window = float_and_drag_active_window
|
||||
ctx.float_and_resize_active_window = float_and_resize_active_window
|
||||
ctx.toggle_pinned_active_window = toggle_pinned_active_window
|
||||
ctx.schedule_scratchpad_geometry = schedule_scratchpad_geometry
|
||||
ctx.hide_scratchpad_window = hide_scratchpad_window
|
||||
ctx.show_scratchpad_window = show_scratchpad_window
|
||||
ctx.scratchpad_is_visible = scratchpad_is_visible
|
||||
ctx.active_scratchpad_windows = active_scratchpad_windows
|
||||
ctx.hide_active_scratchpads = hide_active_scratchpads
|
||||
ctx.refresh_active_scratchpad_geometries = refresh_active_scratchpad_geometries
|
||||
ctx.refresh_active_scratchpad_geometries_later = refresh_active_scratchpad_geometries_later
|
||||
ctx.refresh_shell_workarea_and_scratchpads = refresh_shell_workarea_and_scratchpads
|
||||
ctx.adopt_matching_scratchpad_window = adopt_matching_scratchpad_window
|
||||
ctx.current_minimized_windows = current_minimized_windows
|
||||
ctx.restore_minimized_window = restore_minimized_window
|
||||
ctx.window_picker_candidates_for = window_picker_candidates_for
|
||||
ctx.activate_window_picker_candidate = activate_window_picker_candidate
|
||||
ctx.enter_window_picker = enter_window_picker
|
||||
ctx.gather_focused_class = gather_focused_class
|
||||
ctx.focus_next_class = focus_next_class
|
||||
ctx.show_active_window_info = show_active_window_info
|
||||
ctx.raise_or_spawn = raise_or_spawn
|
||||
ctx.minimize_active_window = minimize_active_window
|
||||
ctx.restore_last_minimized = restore_last_minimized
|
||||
ctx.restore_all_minimized = restore_all_minimized
|
||||
ctx.minimize_other_classes = minimize_other_classes
|
||||
ctx.restore_focused_class = restore_focused_class
|
||||
ctx.toggle_scratchpad = toggle_scratchpad
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -20,5 +20,5 @@ Avoid dropping scripts in `~/bin` or `~/.local/bin` unless the user explicitly a
|
||||
|
||||
- Existing rofi scripts live in `../dotfiles/lib/bin/` (e.g. `rofi_command.sh`).
|
||||
- Keybind locations:
|
||||
- Hyprland: `../dotfiles/config/hypr/hyprland.lua`
|
||||
- Hyprland: `../dotfiles/config/hypr/hyprland/binds.lua`
|
||||
- XMonad: `../dotfiles/config/xmonad/xmonad.hs`
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
{
|
||||
pkgs,
|
||||
hyprlandConfig,
|
||||
hyprlandConfigDir,
|
||||
}:
|
||||
pkgs.runCommand "hyprland-config-syntax" {
|
||||
nativeBuildInputs = [pkgs.lua5_4];
|
||||
} ''
|
||||
cp ${hyprlandConfig} hyprland.lua
|
||||
luac -p hyprland.lua
|
||||
cp -r ${hyprlandConfigDir}/. .
|
||||
chmod -R +w .
|
||||
luac -p hyprland.lua hyprland/*.lua
|
||||
|
||||
if grep -n 'hyprctl' hyprland.lua | grep -v 'hyprctl reload' | grep -v 'hyprctl eval' | grep -v 'hyprctl_eval' | grep -v 'hyprctl -j monitors'; then
|
||||
echo "hyprland.lua should not shell out to hyprctl for window/workspace manipulation" >&2
|
||||
if grep -Rn 'hyprctl' hyprland.lua hyprland/*.lua | grep -v 'hyprctl reload' | grep -v 'hyprctl eval' | grep -v 'hyprctl_eval' | grep -v 'hyprctl -j monitors'; then
|
||||
echo "Hyprland Lua config should not shell out to hyprctl for window/workspace manipulation" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if grep -nE 'hl[.]dsp.*[)][(][)]' hyprland.lua; then
|
||||
echo "hyprland.lua should use hl.dispatch(...) instead of calling dispatcher objects directly" >&2
|
||||
if grep -RnE 'hl[.]dsp.*[)][(][)]' hyprland.lua hyprland/*.lua; then
|
||||
echo "Hyprland Lua config should use hl.dispatch(...) instead of calling dispatcher objects directly" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
@@ -55,15 +55,16 @@ in {
|
||||
hypr-workspace-history = inputs.hypr-workspace-history.packages.${system}.hypr-workspace-history;
|
||||
hyprland-config-syntax = import ../checks/hyprland-config-syntax {
|
||||
inherit pkgs;
|
||||
hyprlandConfig = ../../dotfiles/config/hypr/hyprland.lua;
|
||||
hyprlandConfigDir = ../../dotfiles/config/hypr;
|
||||
};
|
||||
hyprland-verify-config = let
|
||||
hyprlandPackage = inputs.hyprland.packages.${system}.hyprland;
|
||||
hyprNStackPackage = inputs.hyprNStack.packages.${system}.hyprNStack;
|
||||
in
|
||||
pkgs.runCommand "hyprland-lua-verify-config" {} ''
|
||||
cp ${../../dotfiles/config/hypr/hyprland.lua} hyprland.lua
|
||||
substituteInPlace hyprland.lua \
|
||||
cp -r ${../../dotfiles/config/hypr}/. .
|
||||
chmod -R +w .
|
||||
substituteInPlace hyprland/settings.lua \
|
||||
--replace-fail /run/current-system/sw/lib/libhyprNStack.so \
|
||||
${hyprNStackPackage}/lib/libhyprNStack.so
|
||||
export XDG_RUNTIME_DIR="$TMPDIR/runtime"
|
||||
|
||||
Reference in New Issue
Block a user