330 lines
9.9 KiB
Lua
330 lines
9.9 KiB
Lua
-- All keybindings live in this module so conflicts can be audited in one
|
|
-- place. Shared values come from settings; command wrapping comes from lib.
|
|
local commands = require("lib.commands")
|
|
local settings = require("config.settings")
|
|
|
|
local main = settings.modifiers.main
|
|
local hyper = settings.modifiers.hyper
|
|
|
|
-- Build normalized bind strings and avoid hand-written separator mistakes such
|
|
-- as doubled `+` characters.
|
|
local function chord(...)
|
|
return table.concat({ ... }, " + ")
|
|
end
|
|
|
|
-- Stage uses vertical navigation to move through its preview ordering. Keep
|
|
-- the ordinary directional-focus behavior for every other tiled layout.
|
|
local function focus_or_stage(direction, stage_message)
|
|
local focus = hl.dsp.focus({ direction = direction })
|
|
|
|
if not stage_message then
|
|
return focus
|
|
end
|
|
|
|
return function()
|
|
local workspace = hl.get_active_special_workspace() or hl.get_active_workspace()
|
|
|
|
if workspace and workspace.tiled_layout == "stage" then
|
|
hl.dispatch(hl.dsp.layout(stage_message))
|
|
return
|
|
end
|
|
|
|
hl.dispatch(focus)
|
|
end
|
|
end
|
|
|
|
local tiled_layouts = { "dwindle", "master", "scrolling", "monocle", "stage" }
|
|
local active_workspace_layout_rules = {}
|
|
|
|
local function cycle_active_workspace_layout()
|
|
local workspace = hl.get_active_special_workspace() or hl.get_active_workspace()
|
|
if not workspace or workspace.special then
|
|
return
|
|
end
|
|
|
|
local next_index = 1
|
|
for index, layout in ipairs(tiled_layouts) do
|
|
if layout == workspace.tiled_layout then
|
|
next_index = index % #tiled_layouts + 1
|
|
break
|
|
end
|
|
end
|
|
|
|
local selector = workspace.config_name
|
|
if not selector or selector == "" then
|
|
selector = tostring(workspace.id)
|
|
end
|
|
|
|
local previous_rule = active_workspace_layout_rules[workspace.id]
|
|
if previous_rule then
|
|
previous_rule:set_enabled(false)
|
|
end
|
|
|
|
active_workspace_layout_rules[workspace.id] = hl.workspace_rule({
|
|
workspace = selector,
|
|
layout = tiled_layouts[next_index],
|
|
})
|
|
end
|
|
|
|
-- Generate matching focus/move bindings for numbered workspaces. Workspace 10
|
|
-- maps to the 0 key because `% 10` returns zero.
|
|
local function bind_numbered_workspaces(modifier, move_modifier, count)
|
|
for workspace = 1, count do
|
|
local key = workspace % 10
|
|
|
|
hl.bind(
|
|
chord(modifier, key),
|
|
hl.dsp.focus({ workspace = workspace }),
|
|
{ description = "Switch to workspace " .. workspace }
|
|
)
|
|
hl.bind(
|
|
chord(modifier, move_modifier, key),
|
|
hl.dsp.window.move({ workspace = workspace }),
|
|
{ description = "Send focused window to workspace " .. workspace }
|
|
)
|
|
end
|
|
end
|
|
|
|
-- Core application and window-management shortcuts.
|
|
hl.bind(
|
|
chord(main, "SPACE"),
|
|
hl.dsp.exec_cmd("noctalia msg panel-toggle launcher"),
|
|
{ description = "Toggle Noctalia launcher" }
|
|
)
|
|
hl.bind(
|
|
chord(main, "W"),
|
|
hl.dsp.window.close(),
|
|
{ description = "Close focused window" }
|
|
)
|
|
hl.bind(
|
|
chord(main, "Q"),
|
|
hl.dsp.exec_cmd(commands.uwsm(settings.programs.terminal)),
|
|
{ description = "Open terminal" }
|
|
)
|
|
hl.bind(
|
|
chord(main, "E"),
|
|
hl.dsp.exec_cmd(commands.uwsm(settings.programs.file_manager)),
|
|
{ description = "Open file manager" }
|
|
)
|
|
hl.bind(
|
|
chord(main, "V"),
|
|
hl.dsp.window.float({ action = "toggle" }),
|
|
{ description = "Toggle focused window floating" }
|
|
)
|
|
hl.bind(
|
|
chord(main, "R"),
|
|
hl.dsp.exec_cmd(settings.programs.launcher),
|
|
{ description = "Open application launcher" }
|
|
)
|
|
hl.bind(
|
|
chord(main, "P"),
|
|
hl.dsp.window.pseudo(),
|
|
{ description = "Toggle pseudo-tiling for focused window" }
|
|
)
|
|
hl.bind(
|
|
chord(main, "J"),
|
|
hl.dsp.layout("togglesplit"),
|
|
{ description = "Toggle dwindle split direction" }
|
|
)
|
|
|
|
-- One direction table drives arrow-key focus, Hyper+WASD focus/movement, and
|
|
-- moving the active workspace between adjacent monitors.
|
|
local directions = {
|
|
{ arrow = "left", key = "A", direction = "l", monitor = "left" },
|
|
{ arrow = "right", key = "D", direction = "r", monitor = "right" },
|
|
{ arrow = "up", key = "W", direction = "u", monitor = "above", stage_message = "cycleprev" },
|
|
{ arrow = "down", key = "S", direction = "d", monitor = "below", stage_message = "cyclenext" },
|
|
}
|
|
|
|
for _, direction in ipairs(directions) do
|
|
local description = "Focus window " .. direction.arrow
|
|
if direction.stage_message then
|
|
description = description .. " / navigate Stage"
|
|
end
|
|
|
|
hl.bind(
|
|
chord(main, direction.arrow),
|
|
hl.dsp.focus({ direction = direction.arrow }),
|
|
{ description = "Focus window " .. direction.arrow }
|
|
)
|
|
hl.bind(
|
|
chord(hyper, direction.key),
|
|
focus_or_stage(direction.direction, direction.stage_message),
|
|
{ description = description }
|
|
)
|
|
hl.bind(
|
|
chord(hyper, "CTRL", direction.key),
|
|
hl.dsp.window.move({ direction = direction.direction }),
|
|
{ description = "Move focused window " .. direction.arrow }
|
|
)
|
|
hl.bind(
|
|
chord(hyper, main, direction.key),
|
|
hl.dsp.workspace.move({ monitor = direction.direction }),
|
|
{ description = "Move workspace to monitor " .. direction.monitor }
|
|
)
|
|
end
|
|
|
|
-- Cycle only the active workspace through the configured tiled layouts.
|
|
hl.bind(
|
|
chord(hyper, "grave"),
|
|
cycle_active_workspace_layout,
|
|
{ description = "Cycle active workspace layout" }
|
|
)
|
|
|
|
bind_numbered_workspaces(main, "SHIFT", 10)
|
|
bind_numbered_workspaces(hyper, "CTRL", 9)
|
|
|
|
-- Cycle workspaces with the wheel and manipulate windows with mouse buttons.
|
|
hl.bind(
|
|
chord(main, "mouse_down"),
|
|
hl.dsp.focus({ workspace = "e+1" }),
|
|
{ description = "Switch to next workspace" }
|
|
)
|
|
hl.bind(
|
|
chord(main, "mouse_up"),
|
|
hl.dsp.focus({ workspace = "e-1" }),
|
|
{ description = "Switch to previous workspace" }
|
|
)
|
|
hl.bind(
|
|
chord(main, "mouse:272"),
|
|
hl.dsp.window.drag(),
|
|
{ mouse = true, description = "Drag focused window" }
|
|
)
|
|
hl.bind(
|
|
chord(main, "mouse:273"),
|
|
hl.dsp.window.resize(),
|
|
{ mouse = true, description = "Resize focused window" }
|
|
)
|
|
|
|
-- Locked media keys also work while the session is locked. Volume and
|
|
-- brightness binds repeat while held; playback controls fire once.
|
|
local media_binds = {
|
|
{
|
|
key = "XF86AudioRaiseVolume",
|
|
command = "wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+",
|
|
description = "Increase output volume",
|
|
repeating = true,
|
|
},
|
|
{
|
|
key = "XF86AudioLowerVolume",
|
|
command = "wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-",
|
|
description = "Decrease output volume",
|
|
repeating = true,
|
|
},
|
|
{
|
|
key = "XF86AudioMute",
|
|
command = "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle",
|
|
description = "Toggle output mute",
|
|
repeating = true,
|
|
},
|
|
{
|
|
key = "XF86AudioMicMute",
|
|
command = "wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle",
|
|
description = "Toggle microphone mute",
|
|
repeating = true,
|
|
},
|
|
{
|
|
key = "XF86MonBrightnessUp",
|
|
command = "brightnessctl -e4 -n2 set 5%+",
|
|
description = "Increase display brightness",
|
|
repeating = true,
|
|
},
|
|
{
|
|
key = "XF86MonBrightnessDown",
|
|
command = "brightnessctl -e4 -n2 set 5%-",
|
|
description = "Decrease display brightness",
|
|
repeating = true,
|
|
},
|
|
{
|
|
key = "XF86AudioNext",
|
|
command = "playerctl next",
|
|
description = "Play next media track",
|
|
},
|
|
{
|
|
key = "XF86AudioPause",
|
|
command = "playerctl play-pause",
|
|
description = "Toggle media playback",
|
|
},
|
|
{
|
|
key = "XF86AudioPlay",
|
|
command = "playerctl play-pause",
|
|
description = "Toggle media playback",
|
|
},
|
|
{
|
|
key = "XF86AudioPrev",
|
|
command = "playerctl previous",
|
|
description = "Play previous media track",
|
|
},
|
|
}
|
|
|
|
for _, bind in ipairs(media_binds) do
|
|
hl.bind(bind.key, hl.dsp.exec_cmd(bind.command), {
|
|
locked = true,
|
|
repeating = bind.repeating,
|
|
description = bind.description,
|
|
})
|
|
end
|
|
|
|
-- Generate focus/move shortcuts for every named workspace in settings.lua.
|
|
for _, workspace in ipairs(settings.named_workspaces) do
|
|
hl.bind(
|
|
chord(hyper, workspace.key),
|
|
hl.dsp.focus({ workspace = workspace.selector }),
|
|
{ description = "Switch to " .. workspace.name .. " workspace" }
|
|
)
|
|
hl.bind(
|
|
chord(hyper, "CTRL", workspace.key),
|
|
hl.dsp.window.move({ workspace = workspace.selector }),
|
|
{ description = "Send focused window to " .. workspace.name .. " workspace" }
|
|
)
|
|
end
|
|
|
|
-- Promote the focused client when a workspace is using master layout.
|
|
hl.bind(
|
|
chord(hyper, "M"),
|
|
hl.dsp.layout("swapwithmaster master ignoremaster"),
|
|
{ description = "Promote to master" }
|
|
)
|
|
|
|
|
|
-- Toggle ScrollOverview with SUPER+g
|
|
hl.bind(
|
|
chord(hyper, "SPACE"), function()
|
|
hl.plugin.scrolloverview.overview("toggle all")
|
|
end)
|
|
|
|
-- Toggle a focused client into an 80%-sized centered floating mode. Returning
|
|
-- it to tiled mode lets the active layout restore its previous geometry.
|
|
hl.bind(chord(hyper, "F"), function()
|
|
local window = hl.get_active_window()
|
|
local monitor = hl.get_active_monitor()
|
|
|
|
if not window or not monitor then
|
|
return
|
|
end
|
|
|
|
local was_tiled = not window.floating
|
|
hl.dispatch(hl.dsp.window.float({ action = "toggle" }))
|
|
|
|
if not was_tiled then
|
|
return
|
|
end
|
|
|
|
hl.dispatch(hl.dsp.window.resize({
|
|
x = math.floor(monitor.width * 0.80),
|
|
y = math.floor(monitor.height * 0.80),
|
|
relative = false,
|
|
}))
|
|
hl.dispatch(hl.dsp.window.center())
|
|
hl.dispatch(hl.dsp.window.alter_zorder({ mode = "top" }))
|
|
end, {
|
|
description = "Toggle focused floating window",
|
|
})
|
|
|
|
-- Open the HyprCapture plugin's interactive region/window picker.
|
|
hl.bind(
|
|
chord(main, "SHIFT", "S"),
|
|
hl.plugin.hyprcapture.open,
|
|
{ description = "HyprCapture window capture" }
|
|
)
|