From 2a737ed6dfc2380947d55bdf5c14e28aa560053e Mon Sep 17 00:00:00 2001 From: yage Date: Fri, 23 Oct 2026 16:17:23 -0600 Subject: [PATCH] first pass of hyprland config --- config/README.md | 16 ++ config/bin/README.md | 48 ++++++ config/desktop/hyprland/config.lua | 41 +++++ .../hyprland/config.lua.d/00-hyprland.lua | 37 ++++ .../01-hyprland-high-dpi-scaling.lua | 25 +++ .../config.lua.d/09-hyprland-animations.lua | 12 ++ .../hyprland/config.lua.d/10-dwindle.lua | 79 +++++++++ .../config.lua.d/11-dwindle-bindings.lua | 44 +++++ .../config.lua.d/12-dwindle-resize-mode.lua | 28 +++ .../config.lua.d/15-dwindle-i3-scratchpad.lua | 128 ++++++++++++++ .../hyprland/config.lua.d/80-workspaces.lua | 32 ++++ .../config.lua.d/81-workspaces-animations.lua | 34 ++++ .../config.lua.d/82-workspaces-gaps.lua | 73 ++++++++ .../hyprland/config.lua.d/90-key-bindings.lua | 56 ++++++ .../hyprland/config.lua.d/90-window-rules.lua | 25 +++ .../config.lua.d/98-host-local-overrides.lua | 18 ++ .../hyprland/config.lua.d/99-autostart.lua | 12 ++ config/desktop/hyprland/scwrypts.lua | 78 +++++++++ config/desktop/hyprland/session | 15 ++ config/{xinitrc.i3wm => desktop/i3wm/session} | 5 +- config/desktop/kde/session | 8 + config/desktop/server/common.zsh | 47 +++++ config/desktop/server/wayland.zsh | 5 + .../server/x11.zsh} | 31 +--- config/sfx.map | 17 ++ config/user/X11/xinitrc | 2 +- config/user/tmux/tmux.conf | 4 +- config/xinitrc.kde | 5 - .../desktop/wayland/hyprland/eval.module.zsh | 40 +++++ .../hyprland/get-window-addresses.module.zsh | 41 +++++ .../wayland/hyprland/hyprland.module.zsh | 10 ++ .../desktop/wayland/hyprland/launch-or-show | 163 ++++++++++++++++++ .../dotwryn/desktop/wayland/hyprland/lock | 22 +++ .../dotwryn/desktop/wayland/hyprland/logout | 20 +++ .../desktop/wayland/hyprland/screenshot | 26 +++ .../desktop/wayland/hyprland/set-background | 72 ++++++++ .../dotwryn/desktop/wayland/sync-gtk-settings | 45 +++++ scwrypts/media/audio/play-sfx.module.zsh | 21 +-- scwrypts/remote/tmux/tmux.conf | 1 + 39 files changed, 1334 insertions(+), 52 deletions(-) create mode 100644 config/README.md create mode 100644 config/bin/README.md create mode 100644 config/desktop/hyprland/config.lua create mode 100644 config/desktop/hyprland/config.lua.d/00-hyprland.lua create mode 100644 config/desktop/hyprland/config.lua.d/01-hyprland-high-dpi-scaling.lua create mode 100644 config/desktop/hyprland/config.lua.d/09-hyprland-animations.lua create mode 100644 config/desktop/hyprland/config.lua.d/10-dwindle.lua create mode 100644 config/desktop/hyprland/config.lua.d/11-dwindle-bindings.lua create mode 100644 config/desktop/hyprland/config.lua.d/12-dwindle-resize-mode.lua create mode 100644 config/desktop/hyprland/config.lua.d/15-dwindle-i3-scratchpad.lua create mode 100644 config/desktop/hyprland/config.lua.d/80-workspaces.lua create mode 100644 config/desktop/hyprland/config.lua.d/81-workspaces-animations.lua create mode 100644 config/desktop/hyprland/config.lua.d/82-workspaces-gaps.lua create mode 100644 config/desktop/hyprland/config.lua.d/90-key-bindings.lua create mode 100644 config/desktop/hyprland/config.lua.d/90-window-rules.lua create mode 100644 config/desktop/hyprland/config.lua.d/98-host-local-overrides.lua create mode 100644 config/desktop/hyprland/config.lua.d/99-autostart.lua create mode 100644 config/desktop/hyprland/scwrypts.lua create mode 100755 config/desktop/hyprland/session rename config/{xinitrc.i3wm => desktop/i3wm/session} (58%) mode change 100644 => 100755 create mode 100755 config/desktop/kde/session create mode 100644 config/desktop/server/common.zsh create mode 100644 config/desktop/server/wayland.zsh rename config/{xinitrc.common => desktop/server/x11.zsh} (52%) create mode 100644 config/sfx.map delete mode 100644 config/xinitrc.kde create mode 100644 scwrypts/dotwryn/desktop/wayland/hyprland/eval.module.zsh create mode 100644 scwrypts/dotwryn/desktop/wayland/hyprland/get-window-addresses.module.zsh create mode 100644 scwrypts/dotwryn/desktop/wayland/hyprland/hyprland.module.zsh create mode 100755 scwrypts/dotwryn/desktop/wayland/hyprland/launch-or-show create mode 100755 scwrypts/dotwryn/desktop/wayland/hyprland/lock create mode 100755 scwrypts/dotwryn/desktop/wayland/hyprland/logout create mode 100755 scwrypts/dotwryn/desktop/wayland/hyprland/screenshot create mode 100755 scwrypts/dotwryn/desktop/wayland/hyprland/set-background create mode 100755 scwrypts/dotwryn/desktop/wayland/sync-gtk-settings diff --git a/config/README.md b/config/README.md new file mode 100644 index 0000000..572dee4 --- /dev/null +++ b/config/README.md @@ -0,0 +1,16 @@ +# `config` + +My tool and machine configuration ranging from `~/.config` symlinks to libraries which support my daily workflows. + +The [`setup`](../setup) and [`scwrypts`](../scwrypts) handle the setup / maintenance details, but contained herein: + + +| Submodule | Description | +| ------------------------------------ | -------------------------------------------------------------------- | +| [`bin`](./bin) | goes on my `${PATH}`; used in limited cases | +| [`colorschemes`](./colorschemes) | my colorscheme YAML definitions which style all my desktop apps; uses the [spec established in `dotwryn/scwrypts`](../scwrypts/dotwryn/desktop/colorscheme/coloscheme.module.zsh) | +| [`desktop`](./desktop) | utilities which configure, support, or launch a desktop environment | +| [`etc`](./etc) | config files linked back to `/etc` on the local system | +| [`host-overrides`](./host-overrides) | hostname-based values overrides for `etc`, `user`, and `bin` | +| [`user`](./user) | config files linked back to `${XDG_CONFIG_HOME:-${HOME}/.config` | + diff --git a/config/bin/README.md b/config/bin/README.md new file mode 100644 index 0000000..2ba2aba --- /dev/null +++ b/config/bin/README.md @@ -0,0 +1,48 @@ +# config : bin + +This directory contains shell one-offs which solve focused problems. +Generally, `scwrypts` are a better source for long-term maintenance, but, for a _few selected scenarios_, a raw shell script is preferred. +This is typically due to: + +1. The simplicity of the script outweighs the maintenance overhead for an equivalent `scwrypt` +2. The overhead of `scwrypts` causes excessive slowdowns for quick, simple tasks + +## Reference / Usage + +All scripts in this directory support and can be used in one of two ways: + +1. Direct reference through `${DOTWRYN}/config/bin/${script_name}` +2. Symlinked on the user's `${PATH}` + +### Direct Reference + +Anytime a raw script is referenced throughout the `dotwryn` repository, it should be referenced with _direct reference_. +`dotwryn`-internal operations must not assume the user has a symlink to any of these functions. + +### Symlink + +An API for human users! +All the scripts in this directory allow for symlinking. + +```zsh +# you can decide where it goes +ln -s "${DOTWRYN}/config/bin/${script_name}" "${somewhere_on_users_path}/${local_script_name}" + +# since ~/.local/bin is on MY ${PATH} (and I want it to have the same name) +cd ~/.local/bin +ln -s "${DOTWRYN}/config/bin/${script_name}" +``` + + diff --git a/config/desktop/hyprland/config.lua b/config/desktop/hyprland/config.lua new file mode 100644 index 0000000..3996874 --- /dev/null +++ b/config/desktop/hyprland/config.lua @@ -0,0 +1,41 @@ +-- +-- dotwryn hyprland configuration library +-- + +local DOTWRYN = os.getenv("DOTWRYN") +local dotwryn = { + DOTWRYN = DOTWRYN, + + colorscheme = dofile(DOTWRYN .. "/scwrypts/dotwryn/desktop/colorscheme/colorscheme.lua").load(), + scwrypts = dofile(DOTWRYN .. "/config/desktop/hyprland/scwrypts.lua"), + + font = { + family = table.concat({ + "Pokémoji Color", + "Monaspace Argon", + }, ", "), + + size = 11, + }, +} + +--------------------------------------------------------------------- + +local config_filepaths = {} + +local config_dir = (DOTWRYN .. "/config/desktop/hyprland/config.lua.d") +local config_dir_quoted = config_dir:gsub("'", "'\\''") + +assert(io.open(config_dir, "r"), "error reading hyprland config.lua.d : " .. config_dir) + +local find = io.popen("find '" .. config_dir_quoted .. "' -mindepth 1 -maxdepth 1 -type f -name '*.lua'") +for config_filepath in find:lines() do + table.insert(config_filepaths, config_filepath) +end +find:close() + +table.sort(config_filepaths) + +for _, config_filepath in ipairs(config_filepaths) do + assert(loadfile(config_filepath))(dotwryn) +end diff --git a/config/desktop/hyprland/config.lua.d/00-hyprland.lua b/config/desktop/hyprland/config.lua.d/00-hyprland.lua new file mode 100644 index 0000000..fd1229a --- /dev/null +++ b/config/desktop/hyprland/config.lua.d/00-hyprland.lua @@ -0,0 +1,37 @@ +local dotwryn = ... -- ../config.lua +--------------------------------------------------------------------- +-- +-- hyprland-base configuration +-- + +hl.config({ + ecosystem = { + no_update_news = true, + no_donation_nag = true, + }, + + input = { + kb_layout = "us", + }, + + misc = { + font_family = dotwryn.font.family, + force_default_wallpaper = 0, + disable_hyprland_logo = true, + disable_splash_rendering = true, + }, +}) + +hl.monitor({ + output = "", + mode = "preferred", + position = "auto", + scale = "auto", +}) + +------------------------------------------ + +hl.bind("SUPER + SHIFT + e", hl.dsp.exec_cmd("qs ipc call logout show")) +hl.bind("CTRL + ALT + Delete", hl.dsp.exit()) +hl.bind("SUPER + SHIFT + r", hl.dsp.exec_cmd("hyprctl reload")) +hl.bind("SUPER + SHIFT + c", hl.dsp.exec_cmd("hyprctl reload")) diff --git a/config/desktop/hyprland/config.lua.d/01-hyprland-high-dpi-scaling.lua b/config/desktop/hyprland/config.lua.d/01-hyprland-high-dpi-scaling.lua new file mode 100644 index 0000000..27cc798 --- /dev/null +++ b/config/desktop/hyprland/config.lua.d/01-hyprland-high-dpi-scaling.lua @@ -0,0 +1,25 @@ +local dotwryn = ... -- ../config.lua +--------------------------------------------------------------------- +-- +-- high-dpi scaling : XWayland apps render at 1x and scale themselves +-- + +hl.config({ + xwayland = { + force_zero_scaling = true, + }, +}) + +-- force_zero_scaling leaves steam at 1x; match the active monitor's scale instead +local set_steam_scaling = function() + local monitor = hl.get_active_monitor() + if monitor == nil or monitor.scale == nil or monitor.scale <= 0 then return end + + hl.env("STEAM_FORCE_DESKTOPUI_SCALING", tostring(monitor.scale)) +end + +set_steam_scaling() + +for _, event in ipairs({ "monitor.added", "monitor.removed", "monitor.layout_changed" }) do + hl.on(event, set_steam_scaling) +end diff --git a/config/desktop/hyprland/config.lua.d/09-hyprland-animations.lua b/config/desktop/hyprland/config.lua.d/09-hyprland-animations.lua new file mode 100644 index 0000000..73ee702 --- /dev/null +++ b/config/desktop/hyprland/config.lua.d/09-hyprland-animations.lua @@ -0,0 +1,12 @@ +local dotwryn = ... -- ../config.lua +--------------------------------------------------------------------- + +hl.config({ animations = { enabled = true } }) + +hl.curve("easeOutQuint", { type = "bezier", points = { {0.23, 1}, {0.32, 1} } }) + +hl.animation({ leaf = "global", enabled = false }) +hl.animation({ leaf = "windows", enabled = true, speed = 3, bezier = "easeOutQuint", style = "slide" }) +hl.animation({ leaf = "layers", enabled = true, speed = 3, bezier = "easeOutQuint", style = "popin" }) +hl.animation({ leaf = "workspaces", enabled = true, speed = 3, bezier = "easeOutQuint", style = "slide" }) +hl.animation({ leaf = "border", enabled = true, speed = 3, bezier = "easeOutQuint" }) diff --git a/config/desktop/hyprland/config.lua.d/10-dwindle.lua b/config/desktop/hyprland/config.lua.d/10-dwindle.lua new file mode 100644 index 0000000..ea36c13 --- /dev/null +++ b/config/desktop/hyprland/config.lua.d/10-dwindle.lua @@ -0,0 +1,79 @@ +local dotwryn = ... -- ../config.lua +--------------------------------------------------------------------- +-- +-- as a long-time i3wm user, my dwindle configuration contains tries to +-- make the transition feel smooth +-- +-- +-- + +local colors = { + active = { + border = dotwryn.colorscheme.get_hex(".dwindle.colors.active.border", ".i3.focused.border", ".material.secondary.500", ".ansi.green.bright"), + text = dotwryn.colorscheme.get_hex(".dwindle.colors.active.text", ".i3.focused.text", ".material.foreground.secondary.500", ".foreground"), + locked = dotwryn.colorscheme.get_hex(".dwindle.colors.active.locked", ".i3.urgent.border", ".material.error.500", ".ansi.red.bright"), + }, + + inactive = { + border = dotwryn.colorscheme.get_hex(".dwindle.colors.inactive.border", ".i3.unfocused.border", ".material.primary.700", ".ansi.yellow.bright"), + text = dotwryn.colorscheme.get_hex(".dwindle.colors.inactive.text", ".i3.unfocused.text", ".material.foreground.primary.700", ".foreground"), + locked = dotwryn.colorscheme.get_hex(".dwindle.colors.inactive.locked", ".i3.inactive.border", ".material.primary.700", ".ansi.yellow.bright"), + }, +} + +hl.config({ + general = { + layout = "dwindle", + + border_size = 4, + gaps_in = 0, + gaps_out = 0, + + resize_on_border = false, + + col = { + active_border = colors.active.border, + inactive_border = colors.inactive.border, + }, + }, + + dwindle = { preserve_split = true }, + input = { follow_mouse = 1 }, + binds = { movefocus_cycles_groupfirst = true }, + + decoration = { + rounding = 0, + shadow = { enabled = false }, + blur = { enabled = false }, + }, + + -- currently treating dwindle "groups" like an i3 "tabbed" + group = { + col = { + border_active = colors.active.border, + border_inactive = colors.inactive.border, + border_locked_active = colors.active.locked, + border_locked_inactive = colors.inactive.locked, + }, + + groupbar = { + font_family = dotwryn.font.family, + font_size = dotwryn.font.size, + height = 22, + gradients = true, + rounding = 0, + gradient_rounding = 0, + render_titles = true, + + text_color = colors.active.text, + text_color_inactive = colors.inactive.text, + + col = { + active = colors.active.border, + inactive = colors.inactive.border, + locked_active = colors.active.locked, + locked_inactive = colors.inactive.locked, + }, + }, + }, +}) diff --git a/config/desktop/hyprland/config.lua.d/11-dwindle-bindings.lua b/config/desktop/hyprland/config.lua.d/11-dwindle-bindings.lua new file mode 100644 index 0000000..e98a1de --- /dev/null +++ b/config/desktop/hyprland/config.lua.d/11-dwindle-bindings.lua @@ -0,0 +1,44 @@ +local dotwryn = ... -- ../config.lua +--------------------------------------------------------------------- +-- +-- i3wm & vim-style window navigation +-- + +-- moves a window through groups to recreate i3wm tab behavior +local move_window = function(direction) + return function() + local window = hl.get_active_window() + local group = window ~= nil and window.group or nil + + if group ~= nil and group.size > 1 and (direction == "left" or direction == "right") then + local at_edge = (direction == "left" and group.current_index <= 1) + or (direction == "right" and group.current_index >= group.size) + if not at_edge then + hl.dispatch(hl.dsp.group.move_window({ forward = direction == "right" })) + return + end + end + + hl.dispatch(hl.dsp.window.move({ direction = direction, group_aware = true })) + end +end + +------------------------------------------ + +for key, direction in pairs({ h = "left", j = "down", k = "up", l = "right" }) do + hl.bind("SUPER + " .. key, hl.dsp.focus({ direction = direction })) + hl.bind("SUPER + SHIFT + " .. key, move_window(direction)) +end + +hl.bind("SUPER + f", hl.dsp.window.fullscreen({ mode = "fullscreen" })) + +hl.bind("SUPER + q", hl.dsp.window.close()) + +-- i3 'layout toggle split' +hl.bind("SUPER + e", hl.dsp.layout("togglesplit")) + +-- i3 'layout tabbed' / 'layout stacking' : both toggle a group +hl.bind("SUPER + w", hl.dsp.group.toggle()) +hl.bind("SUPER + s", hl.dsp.group.toggle()) + +-- (no equivalent) bindsym $mod+BackSpace open diff --git a/config/desktop/hyprland/config.lua.d/12-dwindle-resize-mode.lua b/config/desktop/hyprland/config.lua.d/12-dwindle-resize-mode.lua new file mode 100644 index 0000000..d4edaf1 --- /dev/null +++ b/config/desktop/hyprland/config.lua.d/12-dwindle-resize-mode.lua @@ -0,0 +1,28 @@ +local dotwryn = ... -- ../config.lua +--------------------------------------------------------------------- +-- +-- i3wm-style resize mode +-- + +local resize = function(x, y) + return function() + local monitor = hl.get_active_monitor() + local scale = (monitor ~= nil and monitor.scale ~= nil and monitor.scale > 0) and monitor.scale or 1 + local step_x = monitor ~= nil and math.floor(monitor.width / scale * 0.1) or 10 + local step_y = monitor ~= nil and math.floor(monitor.height / scale * 0.1) or 10 + hl.dispatch(hl.dsp.window.resize({ x = x * step_x, y = y * step_y, relative = true })) + end +end + +hl.bind("SUPER + r", hl.dsp.submap("resize")) + +hl.define_submap("resize", function() + hl.bind("h", resize( 1, 0), { repeating = true }) + hl.bind("j", resize( 0, -1), { repeating = true }) + hl.bind("k", resize( 0, 1), { repeating = true }) + hl.bind("l", resize(-1, 0), { repeating = true }) + + hl.bind("Return", hl.dsp.submap("reset")) + hl.bind("Escape", hl.dsp.submap("reset")) + hl.bind("SUPER + r", hl.dsp.submap("reset")) +end) diff --git a/config/desktop/hyprland/config.lua.d/15-dwindle-i3-scratchpad.lua b/config/desktop/hyprland/config.lua.d/15-dwindle-i3-scratchpad.lua new file mode 100644 index 0000000..f897ff6 --- /dev/null +++ b/config/desktop/hyprland/config.lua.d/15-dwindle-i3-scratchpad.lua @@ -0,0 +1,128 @@ +local dotwryn = ... -- ../config.lua +--------------------------------------------------------------------- +-- +-- i3wm-style floating windows and scratchpad +-- + +hl.bind("SUPER + SHIFT + space", hl.dsp.window.float({ action = "toggle" })) + +hl.bind("SUPER + mouse:272", hl.dsp.window.drag(), { mouse = true }) +hl.bind("SUPER + mouse:273", hl.dsp.window.resize(), { mouse = true }) + +local SCRATCHPAD = "special:scratchpad" + +local SCRATCHPAD_FILTERS = { + terminal = { "^Alacritty$", "^kitty$" }, + games = { "^steam$", "^dolphin%-emu$", "^Visualboyadvance%-m$", "^steam_app_" }, +} + +local matches_filter = function(class, filter) + if filter == nil then + for name, _ in pairs(SCRATCHPAD_FILTERS) do + if matches_filter(class, name) then return false end + end + return true + end + for _, pattern in ipairs(SCRATCHPAD_FILTERS[filter]) do + if class ~= nil and class:match(pattern) then return true end + end + return false +end + +local workspace_selector = function(workspace) + return workspace.id > 0 and tostring(workspace.id) or ("name:" .. workspace.name) +end + +local scratchpad_shown = {} +local scratchpad_queue = {} + +local scratchpad_enqueue = function(address) + for index, queued in ipairs(scratchpad_queue) do + if queued == address then table.remove(scratchpad_queue, index) break end + end + table.insert(scratchpad_queue, address) +end + +local scratchpad_windows = function() + local stashed = {} + for _, window in ipairs(hl.get_workspace_windows(SCRATCHPAD)) do stashed[window.address] = window end + + local ordered = {} + for _, address in ipairs(scratchpad_queue) do + if stashed[address] ~= nil then + table.insert(ordered, stashed[address]) + stashed[address] = nil + end + end + for address, window in pairs(stashed) do + table.insert(ordered, window) + scratchpad_enqueue(address) + end + return ordered +end + +local scratchpad_fit = function(selector) + local monitor = hl.get_active_monitor() + if monitor ~= nil then + local scale = (monitor.scale ~= nil and monitor.scale > 0) and monitor.scale or 1 + hl.dispatch(hl.dsp.window.resize({ + window = selector, + x = math.floor(monitor.width / scale * 0.75), + y = math.floor(monitor.height / scale * 0.75), + })) + end + hl.dispatch(hl.dsp.window.center({ window = selector })) +end + +hl.bind("SUPER + SHIFT + Tab", function() + local window = hl.get_active_window() + if window == nil then return end + + local selector = "address:" .. window.address + hl.dispatch(hl.dsp.window.float({ window = selector, action = "enable" })) + scratchpad_fit(selector) + hl.dispatch(hl.dsp.window.move({ window = selector, workspace = SCRATCHPAD, follow = false })) + scratchpad_shown[window.address] = nil + scratchpad_enqueue(window.address) +end) + +local scratchpad_show = function(filter) + return function() + local workspace = hl.get_active_workspace() + if workspace == nil then return end + + local active = hl.get_active_window() + local candidates = {} + if filter == nil and active ~= nil and scratchpad_shown[active.address] then + table.insert(candidates, active.address) + end + for address, _ in pairs(scratchpad_shown) do table.insert(candidates, address) end + + for _, address in ipairs(candidates) do + local window = hl.get_window("address:" .. address) + if window == nil or window.workspace == nil or window.workspace.name == SCRATCHPAD then + scratchpad_shown[address] = nil + elseif window.workspace.id == workspace.id and (filter == nil or matches_filter(window.class, filter)) then + hl.dispatch(hl.dsp.window.move({ window = "address:" .. address, workspace = SCRATCHPAD, follow = false })) + scratchpad_shown[address] = nil + scratchpad_enqueue(address) + return + end + end + + for _, window in ipairs(scratchpad_windows()) do + if matches_filter(window.class, filter) then + local selector = "address:" .. window.address + hl.dispatch(hl.dsp.window.move({ window = selector, workspace = workspace_selector(workspace), follow = false })) + scratchpad_fit(selector) + hl.dispatch(hl.dsp.focus({ window = selector })) + scratchpad_shown[window.address] = true + return + end + end + end +end + +hl.bind("SUPER + Tab", scratchpad_show(nil)) +hl.bind("SUPER + x", scratchpad_show("terminal")) +hl.bind("SUPER + SHIFT + x", scratchpad_show("games")) diff --git a/config/desktop/hyprland/config.lua.d/80-workspaces.lua b/config/desktop/hyprland/config.lua.d/80-workspaces.lua new file mode 100644 index 0000000..b66bb51 --- /dev/null +++ b/config/desktop/hyprland/config.lua.d/80-workspaces.lua @@ -0,0 +1,32 @@ +local dotwryn = ... -- ../config.lua +--------------------------------------------------------------------- +-- +-- desktop workspace settings +-- + +dotwryn.workspace_defaults = { + gaps = { inner = "0.5%", outer = "1%" }, +} + +dotwryn.workspaces = { + { key = "1", name = "1" }, + { key = "2", name = "2", gaps = { enabled = false } }, + { key = "3", name = "3" }, + { key = "4", name = "4" }, + { key = "5", name = "5" }, + { key = "6", name = "6" }, + { key = "7", name = "7" }, + { key = "8", name = "8" }, + { key = "9", name = "9" }, + + { key = "g", name = "100", label = "🎮", vertical = true, }, + { key = "0", name = "name:🎧", vertical = true, }, +} + +for _, workspace in ipairs(dotwryn.workspaces) do + if workspace.label ~= nil then + hl.workspace_rule({ workspace = workspace.name, default_name = workspace.label }) + end + + hl.bind("SUPER + SHIFT + " .. workspace.key, hl.dsp.window.move({ workspace = workspace.name, follow = false })) +end diff --git a/config/desktop/hyprland/config.lua.d/81-workspaces-animations.lua b/config/desktop/hyprland/config.lua.d/81-workspaces-animations.lua new file mode 100644 index 0000000..159fd37 --- /dev/null +++ b/config/desktop/hyprland/config.lua.d/81-workspaces-animations.lua @@ -0,0 +1,34 @@ +local dotwryn = ... -- ../config.lua +--------------------------------------------------------------------- +-- +-- workspace navigation with per-switch slide direction +-- + +local VERTICAL_WORKSPACES = {} -- by target ("100", "name:🎧") and display name ("🎮", "🎧") +for _, workspace in ipairs(dotwryn.workspaces) do + if workspace.vertical then + VERTICAL_WORKSPACES[workspace.name] = true + VERTICAL_WORKSPACES[workspace.label or (workspace.name:gsub("^name:", ""))] = true + end +end + +function animate_workspace_focus(name) + local active = hl.get_active_workspace() + local vertical = VERTICAL_WORKSPACES[name] + or (active ~= nil and VERTICAL_WORKSPACES[active.name]) + + hl.animation({ leaf = "workspaces", enabled = true, speed = 3, bezier = "easeOutQuint", style = vertical and "slidevert" or "slide" }) + hl.dispatch(hl.dsp.focus({ workspace = name })) +end + +for _, workspace in ipairs(dotwryn.workspaces) do + if workspace.name ~= "2" then -- (special case below) + hl.bind("SUPER + " .. workspace.key, function() animate_workspace_focus(workspace.name) end) + end +end + +-- workspace 2 is home to the 'omni' tmux session; open a terminal if nothing is attached +hl.bind("SUPER + 2", function() + animate_workspace_focus("2") + hl.dispatch(hl.dsp.exec_cmd("[[ $(tmux -L omni.socket list-clients 2>/dev/null | wc -l) -eq 0 ]] && $TERMINAL")) +end) diff --git a/config/desktop/hyprland/config.lua.d/82-workspaces-gaps.lua b/config/desktop/hyprland/config.lua.d/82-workspaces-gaps.lua new file mode 100644 index 0000000..f76c740 --- /dev/null +++ b/config/desktop/hyprland/config.lua.d/82-workspaces-gaps.lua @@ -0,0 +1,73 @@ +local dotwryn = ... -- ../config.lua +--------------------------------------------------------------------- +-- +-- provides per-workspace gaps settings +-- + +local get_gaps_config = function(workspace) + local gaps = {} + for key, value in pairs(dotwryn.workspace_defaults.gaps) do gaps[key] = value end + for key, value in pairs(workspace.gaps or {}) do gaps[key] = value end + return gaps +end + +local to_pixels = function(value) + local percent = type(value) == "string" and tonumber((value:gsub("%%$", ""))) or nil + if percent == nil then return value end + + local monitor = hl.get_active_monitor() + if monitor == nil then return 0 end -- (recomputed once a monitor shows up) + + local scale = (monitor.scale ~= nil and monitor.scale > 0) and monitor.scale or 1 + return math.floor(math.min(monitor.width, monitor.height) / scale * percent / 100) +end + +local gaps_rules = {} +local set_gaps = function(workspace, inner, outer) + if gaps_rules[workspace] ~= nil then gaps_rules[workspace].rule:set_enabled(false) end + gaps_rules[workspace] = { + inner = inner, + outer = outer, + rule = hl.workspace_rule({ workspace = workspace, gaps_in = inner, gaps_out = outer }), + } +end + +local apply_gaps = function() + for _, workspace in ipairs(dotwryn.workspaces) do + local gaps = get_gaps_config(workspace) + if gaps.enabled == false then + set_gaps(workspace.name, 0, 0) + else + set_gaps(workspace.name, to_pixels(gaps.inner), to_pixels(gaps.outer)) + end + end +end + +apply_gaps() + +for _, event in ipairs({ "monitor.added", "monitor.removed", "monitor.layout_changed" }) do + hl.on(event, apply_gaps) +end + +hl.window_rule({ + name = "smart-no-borders", + match = { float = false, group = false, workspace = "s[false] w[tv1]" }, + border_size = 0, +}) + +local GAPS_STEP = "0.5%" + +local adjust_gaps = function(direction) + return function() + local workspace = hl.get_active_workspace() + if workspace == nil then return end + + local name = workspace.id > 0 and tostring(workspace.id) or ("name:" .. workspace.name) + local current = gaps_rules[name] or { inner = 0, outer = 0 } + local delta = direction * math.max(1, to_pixels(GAPS_STEP)) + set_gaps(name, math.max(0, current.inner + delta), math.max(0, current.outer + delta)) + end +end + +hl.bind("SUPER + z", adjust_gaps( 1), { repeating = true }) +hl.bind("SUPER + SHIFT + z", adjust_gaps(-1), { repeating = true }) diff --git a/config/desktop/hyprland/config.lua.d/90-key-bindings.lua b/config/desktop/hyprland/config.lua.d/90-key-bindings.lua new file mode 100644 index 0000000..de7732e --- /dev/null +++ b/config/desktop/hyprland/config.lua.d/90-key-bindings.lua @@ -0,0 +1,56 @@ +local dotwryn = ... -- ../config.lua +--------------------------------------------------------------------- + +local bind = function(bindings, ...) + for keys, dispatcher in pairs(bindings) do + hl.bind(keys, dispatcher, ...) + end +end + +local set_background = + +--------------------------------------------------------------------- + +bind({ + ["XF86AudioRewind"] = hl.dsp.exec_cmd("playerctl previous"), + ["XF86AudioPrev"] = hl.dsp.exec_cmd("playerctl previous"), + ["XF86AudioForward"] = hl.dsp.exec_cmd("playerctl next"), + ["XF86AudioNext"] = hl.dsp.exec_cmd("playerctl next"), + ["XF86AudioPlay"] = hl.dsp.exec_cmd("playerctl play-pause"), + + ["XF86AudioMute"] = hl.dsp.exec_cmd("wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"), + ["XF86AudioMicMute"] = hl.dsp.exec_cmd("wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle"), +}, { locked = true }) + +------------------------------------------ + +bind({ + ["XF86AudioRaiseVolume"] = hl.dsp.exec_cmd("qs ipc call volume up"), -- ../../../user/quickshell/VolumeOsd.qml "going past 100%" + ["XF86AudioLowerVolume"] = hl.dsp.exec_cmd("wpctl set-volume @DEFAULT_AUDIO_SINK@ 10%-"), +}, { locked = true, repeating = true }) + +------------------------------------------ + +bind({ + ["SUPER + SHIFT + m"] = dotwryn.scwrypts.hyprland.launch_or_show("slack"), + ["SUPER + SHIFT + d"] = dotwryn.scwrypts.hyprland.launch_or_show("fluxer", "-c", "Fluxer"), + ["SUPER + c"] = dotwryn.scwrypts.hyprland.launch_or_show("google-voice-desktop"), + ["SUPER + m"] = dotwryn.scwrypts.hyprland.launch_or_show("youtube-music-desktop-app", "-c", "YouTube Music Desktop App"), + ["SUPER + SHIFT + s"] = dotwryn.scwrypts.hyprland.launch_or_show("signal"), + ["SUPER + a"] = dotwryn.scwrypts.hyprland.launch_or_show("pavucontrol", "-s", "0.5", "-c", "pavucontrol"), + ["SUPER + SHIFT + o"] = dotwryn.scwrypts.hyprland.launch_or_show("obs", "-c", "^obs", "-n", "-l"), + ["SUPER + Return"] = hl.dsp.exec_cmd("$TERMINAL"), + + ["SUPER + p"] = dotwryn.scwrypts.hyprland.set_background("random"), + ["SUPER + SHIFT + p"] = dotwryn.scwrypts.hyprland.set_background("theme"), + + ["SUPER + SHIFT + minus"] = hl.dsp.exec_cmd("pidof hyprlock || hyprlock"), + ["SUPER + space"] = hl.dsp.exec_cmd("rofi -theme $DOTWRYN/config/rofi.theme.rasi -modes drun,run -show run"), + ["SUPER + backslash"] = dotwryn.scwrypts.hyprland.screenshot(), + + -- + -- global application shortcuts (wayland apps can't grab global keys) + -- + + ["ALT + SHIFT + space"] = hl.dsp.exec_cmd("1password --quick-access"), +}) diff --git a/config/desktop/hyprland/config.lua.d/90-window-rules.lua b/config/desktop/hyprland/config.lua.d/90-window-rules.lua new file mode 100644 index 0000000..9fb409e --- /dev/null +++ b/config/desktop/hyprland/config.lua.d/90-window-rules.lua @@ -0,0 +1,25 @@ +local dotwryn = ... -- ../config.lua +--------------------------------------------------------------------- + +hl.window_rule({ name = "float-modal", match = { modal = true }, float = true }) + +hl.window_rule({ + name = "float-dialog-titles", + match = { title = "^(About|Organizer|Preferences|Task Dialog|Toolbox|Web Console).*$" }, + float = true, +}) + +hl.window_rule({ + name = "screenshot-flameshot", -- scwrypts hyprland screenshot + match = { class = "^flameshot$" }, + float = true, + move = "0 0", + pin = true, + no_anim = true, +}) + +hl.window_rule({ + name = "games-workspace", + match = { class = "^steam_app_.*$" }, + workspace = "100", -- 🎮 +}) diff --git a/config/desktop/hyprland/config.lua.d/98-host-local-overrides.lua b/config/desktop/hyprland/config.lua.d/98-host-local-overrides.lua new file mode 100644 index 0000000..d7283b9 --- /dev/null +++ b/config/desktop/hyprland/config.lua.d/98-host-local-overrides.lua @@ -0,0 +1,18 @@ +local dotwryn = ... -- ../config.lua +--------------------------------------------------------------------- + +local hostnamectl = io.popen("hostnamectl --static") +local HOSTNAME = hostnamectl:read("l") +hostnamectl:close() + +local dofile_if_exists = function(path) + local file = io.open(path, "r") + if file == nil then return end + file:close() + dofile(path) +end + +------------------------------------------ + +dofile_if_exists(dotwryn.DOTWRYN .. "/config/local/" .. HOSTNAME .. "/user/hypr/host.lua") +dofile_if_exists((os.getenv("XDG_CONFIG_HOME") or (os.getenv("HOME") .. "/.config")) .. "/hypr/local.lua") diff --git a/config/desktop/hyprland/config.lua.d/99-autostart.lua b/config/desktop/hyprland/config.lua.d/99-autostart.lua new file mode 100644 index 0000000..2bc3be7 --- /dev/null +++ b/config/desktop/hyprland/config.lua.d/99-autostart.lua @@ -0,0 +1,12 @@ +local dotwryn = ... -- ../config.lua +--------------------------------------------------------------------- + +hl.on("hyprland.start", function() + hl.dispatch(dotwryn.scwrypts.hyprland.set_background("theme")) -- (starts hyprpaper if it isn't running) + hl.dispatch(dotwryn.scwrypts.wayland.sync_gtk_settings()) -- gtk theme from settings.ini + hl.exec_cmd("quickshell") + hl.exec_cmd("1password --silent") + hl.dispatch(dotwryn.scwrypts.tmux.omni()) + hl.exec_cmd("/usr/lib/xfce-polkit/xfce-polkit") + hl.exec_cmd("command -v apps && apps") -- host-local background apps ($DOTWRYN/config/local//bin/apps) +end) diff --git a/config/desktop/hyprland/scwrypts.lua b/config/desktop/hyprland/scwrypts.lua new file mode 100644 index 0000000..8a2e500 --- /dev/null +++ b/config/desktop/hyprland/scwrypts.lua @@ -0,0 +1,78 @@ +-- +-- custom (temporary?) interface to scwrypts for hyprland lua +-- + +local scwrypts = function(args_meta, args_scwrypt, environment) + local get_quoted = function(args) + local quoted_args = {} + for _, arg in ipairs(args) do + table.insert(quoted_args, "'" .. arg:gsub("'", "'\\''") .. "'") + end + return table.concat(quoted_args, " ") + end + + local environment_names = {} + for name, _ in pairs(environment or {}) do table.insert(environment_names, name) end + table.sort(environment_names) + + local environment_assignments = {} + for _, name in ipairs(environment_names) do + table.insert(environment_assignments, name .. "=" .. get_quoted({ environment[name] })) + end + + return table.concat({ + table.concat(environment_assignments, " "), + get_quoted({ os.getenv("DOTWRYN") .. "/zsh/plugins/scwrypts/scwrypts" }), + get_quoted(args_meta or {}), + "--", + get_quoted(args_scwrypt or {}), + }, " ") +end + +------------------------------------------ + +local scwrypts_desktop = function(args_meta, args_scwrypt) + return scwrypts(args_meta, args_scwrypt, { + SCWRYPTS_LOG_LEVEL = "0", + DESKTOP__SFX_PATH = os.getenv("HOME") .. "/Media/sfx", + }) +end + +--------------------------------------------------------------------- + +local hyprland = { + launch_or_show = function(program, ...) + return hl.dsp.exec_cmd(scwrypts_desktop({ 'hyprland', 'launch-or-show' }, { program, ... })) + end, + + screenshot = function(...) + return hl.dsp.exec_cmd(scwrypts_desktop({ 'hyprland', 'screenshot' }, { ... })) + end, + + set_background = function(...) + return hl.dsp.exec_cmd(scwrypts_desktop({ 'hyprland', 'set-background' }, { ... })) + end, +} + +local wayland = { + sync_gtk_settings = function(...) + return hl.dsp.exec_cmd(scwrypts({ 'sync-gtk-settings' }, { ... })) + end, +} + +local tmux = { + omni = function() + return hl.dsp.exec_cmd(scwrypts({ 'omni', 'tmux' }, { '--background' })) + end, +} + +--------------------------------------------------------------------- + +return { + scwrypts = scwrypts, + scwrypts_desktop = scwrypts_desktop, + + hyprland = hyprland, + wayland = wayland, + tmux = tmux, +} diff --git a/config/desktop/hyprland/session b/config/desktop/hyprland/session new file mode 100755 index 0000000..df67da1 --- /dev/null +++ b/config/desktop/hyprland/session @@ -0,0 +1,15 @@ +#!/usr/bin/env zsh +# hyprland session; TTY alias is 'starth' +export XDG_SESSION_TYPE=wayland +source "${DOTWRYN}/config/desktop/server/common.zsh" +##################################################################### + +export XDG_CURRENT_DESKTOP=Hyprland +export XDG_SESSION_DESKTOP=Hyprland +export DESKTOP_SESSION=hyprland + +##################################################################### + +systemctl --user start hyprland-session.target +cd; start-hyprland +systemctl --user stop hyprland-session.target diff --git a/config/xinitrc.i3wm b/config/desktop/i3wm/session old mode 100644 new mode 100755 similarity index 58% rename from config/xinitrc.i3wm rename to config/desktop/i3wm/session index 43ec976..39bbb72 --- a/config/xinitrc.i3wm +++ b/config/desktop/i3wm/session @@ -1,5 +1,8 @@ #!/bin/zsh -source "${DOTWRYN}/config/xinitrc.common" +# KDE plasma session; TTY alias is 'starti'; also default 'startx' target +export XDG_SESSION_TYPE=x11 +source "${DOTWRYN}/config/desktop/server/common.zsh" +##################################################################### export DESKTOP_SESSION=i3 export XDG_CURRENT_DESKTOP=i3 diff --git a/config/desktop/kde/session b/config/desktop/kde/session new file mode 100755 index 0000000..90d09a6 --- /dev/null +++ b/config/desktop/kde/session @@ -0,0 +1,8 @@ +#!/bin/zsh +# KDE plasma session; TTY alias is 'startk' +export XDG_SESSION_TYPE=x11 +source "$DOTWRYN/config/desktop/server/common.zsh" +##################################################################### + +export DESKTOP_SESSION=plasma +exec startplasma-x11 diff --git a/config/desktop/server/common.zsh b/config/desktop/server/common.zsh new file mode 100644 index 0000000..5eaf59b --- /dev/null +++ b/config/desktop/server/common.zsh @@ -0,0 +1,47 @@ +# +# shared by every desktop session (config/desktop/*/session) +# + +case "${XDG_SESSION_TYPE}" in + ( x11 | wayland ) ;; + ( '' ) + echo "ERROR : XDG_SESSION_TYPE must be set to one of 'x11' or 'wayland' (currently unset)" >&2 + return 1 + ;; + ( * ) + echo "ERROR : XDG_SESSION_TYPE must be 'x11' or 'wayland' (got '${XDG_SESSION_TYPE}')" >&2 + return 1 + ;; +esac + +##################################################################### + +for PREFERRED_TERMINAL in \ + alacritty \ + kitty \ + ; +do + command -v ${PREFERRED_TERMINAL} &>/dev/null \ + && export TERMINAL="$(which ${PREFERRED_TERMINAL})" \ + && break +done +unset PREFERRED_TERMINAL + +##################################################################### + +for PREFERRED_BROWSER in \ + brave \ + firefox \ + google-chrome-stable \ + google-chrome \ + ; +do + command -v ${PREFERRED_BROWSER} &>/dev/null \ + && export BROWSER="$(which ${PREFERRED_BROWSER})" \ + && break +done +unset PREFERRED_BROWSER + +##################################################################### + +source "${DOTWRYN}/config/desktop/server/${XDG_SESSION_TYPE}.zsh" diff --git a/config/desktop/server/wayland.zsh b/config/desktop/server/wayland.zsh new file mode 100644 index 0000000..4250189 --- /dev/null +++ b/config/desktop/server/wayland.zsh @@ -0,0 +1,5 @@ +# +# currently empty; the previous server-level configurations are +# owned by the "Wayland Compositor" instead rather than the server +# +true diff --git a/config/xinitrc.common b/config/desktop/server/x11.zsh similarity index 52% rename from config/xinitrc.common rename to config/desktop/server/x11.zsh index 186c712..c0d2f59 100644 --- a/config/xinitrc.common +++ b/config/desktop/server/x11.zsh @@ -1,32 +1,3 @@ -#!/bin/zsh -##################################################################### - -for PREFERRED_TERMINAL in \ - alacritty \ - kitty \ - ; -do - command -v ${PREFERRED_TERMINAL} &>/dev/null \ - && export TERMINAL="$(which ${PREFERRED_TERMINAL})" \ - && break -done -unset PREFERRED_TERMINAL - -##################################################################### - -for PREFERRED_BROWSER in \ - brave \ - firefox \ - google-chrome-stable \ - google-chrome \ - ; -do - command -v ${PREFERRED_BROWSER} &>/dev/null \ - && export BROWSER="$(which ${PREFERRED_BROWSER})" \ - && break -done -unset PREFERRED_BROWSER - ##################################################################### for XINITRC_FILE in $(find /etc/X11/xinit/xinitrc.d | sort); do source "${XINITRC_FILE}"; done @@ -47,7 +18,7 @@ for BACKGROUND_APPLICATION in \ "${DOTWRYN}/config/local/$(hostnamectl --static)/bin/default" \ ; do - command -v ${BACKGROUND_APPLCATION} && exec ${BACKGROUND_APPLICATION} & + command -v ${BACKGROUND_APPLICATION} && exec ${BACKGROUND_APPLICATION} & done ##################################################################### diff --git a/config/sfx.map b/config/sfx.map new file mode 100644 index 0000000..58b73e1 --- /dev/null +++ b/config/sfx.map @@ -0,0 +1,17 @@ +# sound effect names -> files in DESKTOP__SFX_PATH +# +# shared by: +# - scwrypts 'media audio/play-sfx' (i3, login/dock scripts, automation) +# - quickshell VolumeOsd.qml (hyprland volume / mute feedback) +# +# +backlight yaru-audio-volume-change.oga +gamedock gamedock.oga +homedock homedock.oga +login yaru-desktop-login.oga +logout smooth-desktop-login.oga +mute smooth-dialog-warning.oga +notify yaru-complete.oga +undock yaru-desktop-login.oga +volume yaru-message.oga +volume-max smooth-dialog-warning.oga diff --git a/config/user/X11/xinitrc b/config/user/X11/xinitrc index 3497d14..8621206 100644 --- a/config/user/X11/xinitrc +++ b/config/user/X11/xinitrc @@ -1,3 +1,3 @@ #!/bin/zsh source "${HOME}/.zshrc" &>/dev/null -source "${DOTWRYN}/config/xinitrc.i3wm" +source "${DOTWRYN}/config/desktop/i3wm/session" diff --git a/config/user/tmux/tmux.conf b/config/user/tmux/tmux.conf index 5fa789e..2a06be4 100644 --- a/config/user/tmux/tmux.conf +++ b/config/user/tmux/tmux.conf @@ -7,8 +7,10 @@ set -sg repeat-time 0 set-option -g default-shell /bin/zsh +set-option -ga update-environment HYPRLAND_INSTANCE_SIGNATURE + # quick reload -bind-key r source-file ~/.tmux.conf\; display "TMUX CONFIG RELOADED" +bind-key r source-file ~/.config/tmux/tmux.conf\; display "TMUX CONFIG RELOADED" # force color set -g default-terminal "tmux-256color" diff --git a/config/xinitrc.kde b/config/xinitrc.kde deleted file mode 100644 index 9eac5a1..0000000 --- a/config/xinitrc.kde +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/zsh -source "$DOTWRYN/config/xinitrc.common" - -export DESKTOP_SESSION=plasma -exec startplasma-x11 diff --git a/scwrypts/dotwryn/desktop/wayland/hyprland/eval.module.zsh b/scwrypts/dotwryn/desktop/wayland/hyprland/eval.module.zsh new file mode 100644 index 0000000..4a8231e --- /dev/null +++ b/scwrypts/dotwryn/desktop/wayland/hyprland/eval.module.zsh @@ -0,0 +1,40 @@ +##################################################################### + +DEPENDENCIES+=(hyprctl) + +##################################################################### + +${scwryptsmodule}() { + eval "$(utils.parse.autosetup)" + ########################################## + + local result="$(hyprctl eval "$(printf '%s\n' "${ARGS[@]}")")" + + [[ "${result}" == ok ]] \ + || echo.error "hyprctl eval failed : ${result}" \ + || return 1 +} + +##################################################################### + +${scwryptsmodule}.parse() { return 0; } + +${scwryptsmodule}.parse.locals() { + local ARGS=() +} + +${scwryptsmodule}.parse.usage() { + USAGE__description=' + run hyprland lua in the current hyprland instance (one statement per + argument, evaluated together); fails unless hyprland replies "ok" + ' + + USAGE__args=' + \$@ lua statements (e.g. "hl.dispatch(hl.dsp.window.center({}))") + ' +} + +${scwryptsmodule}.parse.validate() { + [[ "${#ARGS[@]}" -gt 0 ]] \ + || echo.error 'at least one lua statement is required' +} diff --git a/scwrypts/dotwryn/desktop/wayland/hyprland/get-window-addresses.module.zsh b/scwrypts/dotwryn/desktop/wayland/hyprland/get-window-addresses.module.zsh new file mode 100644 index 0000000..d995610 --- /dev/null +++ b/scwrypts/dotwryn/desktop/wayland/hyprland/get-window-addresses.module.zsh @@ -0,0 +1,41 @@ +##################################################################### + +DEPENDENCIES+=(hyprctl jq) + +##################################################################### + +${scwryptsmodule}() { + eval "$(utils.parse.autosetup)" + ########################################## + + hyprctl -j clients \ + | jq -r --arg class "${CLIENT_CLASS}" '.[] | select(.class | test($class; "i")) | .address' \ + ; +} + +##################################################################### + +${scwryptsmodule}.parse() { return 0; } + +${scwryptsmodule}.parse.locals() { + local ARGS=() + local CLIENT_CLASS +} + +${scwryptsmodule}.parse.usage() { + USAGE__description=' + print the address of every hyprland window whose class matches + (one per line; case-insensitive regex) + ' + + USAGE__args=' + \$1 window class regex + ' +} + +${scwryptsmodule}.parse.validate() { + CLIENT_CLASS="${ARGS[1]}" + + [[ "${CLIENT_CLASS}" ]] \ + || echo.error 'window class is required' +} diff --git a/scwrypts/dotwryn/desktop/wayland/hyprland/hyprland.module.zsh b/scwrypts/dotwryn/desktop/wayland/hyprland/hyprland.module.zsh new file mode 100644 index 0000000..be98f7b --- /dev/null +++ b/scwrypts/dotwryn/desktop/wayland/hyprland/hyprland.module.zsh @@ -0,0 +1,10 @@ +# +# interact with the running hyprland instance +# + + +# run hyprland lua (hyprctl eval) with error checking +use desktop/wayland/hyprland/eval --group dotwryn + +# list window addresses matching a class regex +use desktop/wayland/hyprland/get-window-addresses --group dotwryn diff --git a/scwrypts/dotwryn/desktop/wayland/hyprland/launch-or-show b/scwrypts/dotwryn/desktop/wayland/hyprland/launch-or-show new file mode 100755 index 0000000..e232c44 --- /dev/null +++ b/scwrypts/dotwryn/desktop/wayland/hyprland/launch-or-show @@ -0,0 +1,163 @@ +#!/usr/bin/env zsh +use notify +use desktop/wayland/hyprland --group dotwryn + +DEPENDENCIES+=(jq) +##################################################################### + +USAGE__usage+=' [...options...]' + +USAGE__options=" + -c, --client if different from the executable name, the window class (regex) + + -s, --scale (default: 0.8 or 0.5 if screen width >3000px) + -x, --x-offset (default: 0.0) + -y, --y-offset (default: 0.0) + + -a, --always-launch invoke executable even if a window of client-class exists + -n, --no-resize don't resize the window (ignores -sxy flags) + -l, --no-center leave the window wherever it was last positioned + + --has-statusbar-icon (accepted for i3 compatibility; no effect) + " + +USAGE__description=" + Hyprland port of 'desktop/xorg/i3/launch-or-show'. + + Makes it easy to bind appications to key shortcuts without having to + spin up redundant instances. Each application gets its own special + workspace (hyprland's scratchpad), named after the executable: + 1) starts the application if it is not running + 2) moves all instances of the application to its special workspace + 3) (toggle) hides the special workspace if it is showing + 4) (toggle) shows the special workspace (resized and centered) if it is hidden + " + +##################################################################### + +MAIN() { + local SCWRYPTS_NOTIFICATION_ENGINES=(echo notify.desktop) + + local application client_class + local x_offset=0.0 y_offset=0.0 scale + local always_launch=false resize=true center=true + + local _s _p=0 + while [[ "${#}" -gt 0 ]] + do + _s=1 + case "${1}" in + ( -c | --client ) _s=2; client_class="${2}" ;; + ( -x | --x-offset ) _s=2; x_offset="${2}" ;; + ( -y | --y-offset ) _s=2; y_offset="${2}" ;; + ( -s | --scale ) _s=2; scale="${2}" ;; + + ( -a | --always-launch ) always_launch=true ;; + ( -n | --no-resize ) resize=false ;; + ( -l | --no-center ) center=false ;; + + ( --has-statusbar-icon ) ;; + + ( -h | --help ) USAGE; return 0 ;; + + ( * ) + case "$((_p+=1))" in + ( 1 ) application="${1}" ;; + ( * ) echo.error "extra positional argument '${1}'" ;; + esac + ;; + esac + shift "${_s}" || echo.error "missing value for argument '${1}'" || shift "${#}" + done + unset _s _p + + [[ "${application}" ]] || echo.error 'path-executable required' + [[ "${client_class}" ]] || client_class="${application}" + + [[ "${application}" ]] && { + utils.dependencies.check "${application}" \ + || notify.error "${application} not found" + } + + utils.check-errors --fail + + ########################################## + + local special_workspace="special:${application:t}" + + local monitor="$(hyprctl -j monitors | jq -c '.[] | select(.focused)')" + [[ "${scale}" ]] || { + [[ "$(jq -r '.width' <<< "${monitor}")" -gt 3000 ]] \ + && scale=0.5 \ + || scale=0.8 \ + ; + } + + ########################################## + + local addresses=($(dotwryn.desktop.wayland.hyprland.get-window-addresses "${client_class}")) + + [[ "${always_launch}" == true ]] || [[ "${#addresses[@]}" -eq 0 ]] && { + echo.status 'launching application' + hyprctl dispatch "hl.dsp.exec_cmd(\"${application}\")" >/dev/null + + echo.status 'waiting for application window' + local wait_count + for wait_count in {1..50} + do + [[ "$(dotwryn.desktop.wayland.hyprland.get-window-addresses "${client_class}" | wc -l)" -gt "${#addresses[@]}" ]] && break + sleep 0.1 + done + addresses=($(dotwryn.desktop.wayland.hyprland.get-window-addresses "${client_class}")) + } + + [[ "${#addresses[@]}" -gt 0 ]] \ + || notify.error "no '${client_class}' window found for ${application}" \ + || return 1 + + ########################################## + + echo.status 'moving windows to special workspace' + local address lua=() + for address in "${addresses[@]}" + do + lua+=( + "hl.dispatch(hl.dsp.window.float({ window = \"address:${address}\", action = \"enable\" }))" + "hl.dispatch(hl.dsp.window.move({ window = \"address:${address}\", workspace = \"${special_workspace}\", follow = false }))" + ) + done + + local shown_special="$(jq -r '.specialWorkspace.name' <<< "${monitor}")" + [[ "${shown_special}" == "${special_workspace}" ]] && { + echo.status 'hiding special workspace' + lua+=("hl.dispatch(hl.dsp.workspace.toggle_special(\"${application:t}\"))") + dotwryn.desktop.wayland.hyprland.eval "${lua[@]}" + return $? + } + + echo.status 'showing special workspace' + lua+=("hl.dispatch(hl.dsp.workspace.toggle_special(\"${application:t}\"))") + + [[ "${resize}" == true ]] && { + local window_size=($(jq -r \ + --argjson f "${scale}" --argjson x "${x_offset}" --argjson y "${y_offset}" \ + '"\((.width / .scale * $f + $x) | floor) \((.height / .scale * $f + $y) | floor)"' \ + <<< "${monitor}" + )) + echo.debug "window size: ${window_size[*]}" + + for address in "${addresses[@]}" + do + lua+=("hl.dispatch(hl.dsp.window.resize({ window = \"address:${address}\", x = ${window_size[1]}, y = ${window_size[2]} }))") + done + } + + [[ "${center}" == true ]] && { + for address in "${addresses[@]}" + do + lua+=("hl.dispatch(hl.dsp.window.center({ window = \"address:${address}\" }))") + done + } + + dotwryn.desktop.wayland.hyprland.eval "${lua[@]}" +} diff --git a/scwrypts/dotwryn/desktop/wayland/hyprland/lock b/scwrypts/dotwryn/desktop/wayland/hyprland/lock new file mode 100755 index 0000000..a8ced13 --- /dev/null +++ b/scwrypts/dotwryn/desktop/wayland/hyprland/lock @@ -0,0 +1,22 @@ +#!/usr/bin/env zsh +use notify + +DEPENDENCIES+=(hyprlock) +##################################################################### + +USAGE__description=' + Hyprland port of "desktop/xorg/i3/lock"; locks the session with hyprlock + (themed by ~/.config/hypr/hyprlock.conf and the active colorscheme) + ' + +##################################################################### + +MAIN() { + local SCWRYPTS_NOTIFICATION_ENGINES=(echo notify.desktop) + + pgrep -x hyprlock &>/dev/null && return 0 + + hyprlock \ + || notify.error 'something went wrong; unable to lock' \ + ; +} diff --git a/scwrypts/dotwryn/desktop/wayland/hyprland/logout b/scwrypts/dotwryn/desktop/wayland/hyprland/logout new file mode 100755 index 0000000..83fa2ef --- /dev/null +++ b/scwrypts/dotwryn/desktop/wayland/hyprland/logout @@ -0,0 +1,20 @@ +#!/usr/bin/env zsh +use notify + +DEPENDENCIES+=(qs) +##################################################################### + +USAGE__description=' + Hyprland port of "desktop/xorg/i3/logout"; asks for confirmation + (quickshell LogoutPrompt) before exiting hyprland + ' + +##################################################################### + +MAIN() { + local SCWRYPTS_NOTIFICATION_ENGINES=(echo notify.desktop) + + qs ipc call logout show \ + || notify.error 'unable to show logout prompt (is quickshell running?)' \ + ; +} diff --git a/scwrypts/dotwryn/desktop/wayland/hyprland/screenshot b/scwrypts/dotwryn/desktop/wayland/hyprland/screenshot new file mode 100755 index 0000000..50e1855 --- /dev/null +++ b/scwrypts/dotwryn/desktop/wayland/hyprland/screenshot @@ -0,0 +1,26 @@ +#!/usr/bin/env zsh +use notify + +DEPENDENCIES+=(flameshot hyprctl jq) +##################################################################### + +USAGE__description=' + Hyprland equivalent of the i3 flameshot screenshot; freezes the + focused monitor in flameshot to select / annotate / copy / save + ' + +##################################################################### + +MAIN() { + local SCWRYPTS_NOTIFICATION_ENGINES=(echo notify.desktop) + + # flameshot screens are numbered from 0 in output order (hyprland ids are not) + local screen_number + screen_number="$(hyprctl monitors -j | jq -r 'sort_by(.id) | to_entries[] | select(.value.focused) | .key')" + [ "${screen_number}" ] \ + || { notify.error 'unable to determine focused monitor'; return 1; } + + flameshot screen --edit --number "${screen_number}" \ + || notify.error 'unable to take screenshot' \ + ; +} diff --git a/scwrypts/dotwryn/desktop/wayland/hyprland/set-background b/scwrypts/dotwryn/desktop/wayland/hyprland/set-background new file mode 100755 index 0000000..7424a8a --- /dev/null +++ b/scwrypts/dotwryn/desktop/wayland/hyprland/set-background @@ -0,0 +1,72 @@ +#!/usr/bin/env zsh +use notify +use scwrypts/get-realpath + +DEPENDENCIES+=(hyprctl) +REQUIRED_ENV+=(DESKTOP__WALLPAPER_PATH) +##################################################################### + +USAGE__usage+=' [wallpaper]' + +USAGE__args+=' + $1 one of: + random : any wallpaper (default) + theme : the wallpaper named after the active colorscheme (e.g. main.jpg) + (optional \$2 : use this theme name instead) + : a wallpaper file within DESKTOP__WALLPAPER_PATH + ' + +USAGE__description=' + Hyprland port of "desktop/xorg/i3/set-background"; sets the wallpaper + on every monitor through hyprpaper + ' + +##################################################################### + +MAIN() { + local SCWRYPTS_NOTIFICATION_ENGINES=(echo notify.desktop) + + # env values are literal (e.g. '$HOME/Pictures/bg'); resolve before use + local wallpaper_path="$(scwrypts.get-realpath "${DESKTOP__WALLPAPER_PATH}")" + + [[ -d "${wallpaper_path}" ]] \ + || echo.error "no such directory for DESKTOP__WALLPAPER_PATH='${DESKTOP__WALLPAPER_PATH}'" \ + || return 1 + + local selection="${1:-random}" + local wallpaper + + case "${selection}" in + ( random ) + wallpaper="$(find "${wallpaper_path}" -type f | shuf -n1)" + ;; + ( theme ) + # optional $2 : theme name (default: the active theme) + local theme_name="${2:-$(cat -- "${DOTWRYN}/config/colorschemes/active/name.txt" 2>/dev/null)}" + wallpaper="$(find "${wallpaper_path}" -type f -name "${theme_name}.*" | head -n1)" + [[ "${wallpaper}" ]] \ + || echo.error "no wallpaper for theme '${theme_name}' in wallpaper path" \ + || return 2 + ;; + ( * ) + wallpaper="${wallpaper_path}/${selection}" + [[ -f "${wallpaper}" ]] \ + || echo.error "no such wallpaper '${selection}' in wallpaper path" \ + || return 2 + ;; + esac + + [[ "${wallpaper}" ]] \ + || echo.error "no wallpapers found in '${wallpaper_path}'" \ + || return 3 + + pgrep -x hyprpaper &>/dev/null || { + hyprctl dispatch 'hl.dsp.exec_cmd("hyprpaper")' >/dev/null + sleep 1 + } + + # empty monitor : fallback for any monitor without a specific wallpaper + hyprctl hyprpaper wallpaper ", ${wallpaper}, cover" >/dev/null \ + || notify.error "unable to set wallpaper '${wallpaper}'" \ + ; +} diff --git a/scwrypts/dotwryn/desktop/wayland/sync-gtk-settings b/scwrypts/dotwryn/desktop/wayland/sync-gtk-settings new file mode 100755 index 0000000..a63c0ab --- /dev/null +++ b/scwrypts/dotwryn/desktop/wayland/sync-gtk-settings @@ -0,0 +1,45 @@ +#!/usr/bin/env zsh +DEPENDENCIES+=(gsettings) +##################################################################### + +USAGE__description=' + On X11, GTK reads its theme from ~/.config/gtk-3.0/settings.ini (written by + lxappearance). Wayland GTK apps read gsettings instead, so copy the values + across; settings.ini stays the single source of truth for both sessions. + ' + +##################################################################### + +MAIN() { + local settings_file="${XDG_CONFIG_HOME:-${HOME}/.config}/gtk-3.0/settings.ini" + + [[ -f "${settings_file}" ]] \ + || echo.error "no gtk settings found at '${settings_file}'" \ + || return 1 + + local ini_key gsettings_key value + for ini_key gsettings_key in \ + gtk-theme-name gtk-theme \ + gtk-icon-theme-name icon-theme \ + gtk-font-name font-name \ + gtk-cursor-theme-name cursor-theme \ + ; + do + value="$(sed -n "s/^${ini_key}=//p" "${settings_file}" | tail -n1)" + [[ "${value}" ]] || continue + + gsettings set org.gnome.desktop.interface "${gsettings_key}" "${value}" \ + && echo.status "${gsettings_key} : ${value}" \ + || echo.error "unable to set ${gsettings_key}" \ + ; + done + + # libadwaita / portal-aware apps (electron, gtk4) follow color-scheme rather than the theme name + local theme="$(gsettings get org.gnome.desktop.interface gtk-theme)" + case "${theme:l}" in + ( *dark* ) gsettings set org.gnome.desktop.interface color-scheme prefer-dark ;; + ( * ) gsettings set org.gnome.desktop.interface color-scheme default ;; + esac + + [[ "${ERRORS}" -eq 0 ]] +} diff --git a/scwrypts/media/audio/play-sfx.module.zsh b/scwrypts/media/audio/play-sfx.module.zsh index 2b382b5..e02bee3 100644 --- a/scwrypts/media/audio/play-sfx.module.zsh +++ b/scwrypts/media/audio/play-sfx.module.zsh @@ -28,21 +28,14 @@ ${scwryptsmodule}.parse() { [[ ${POSITIONAL_ARGS} -eq 0 ]] || return 0 ((POSITIONAL_ARGS+=1)) - case $1 in - ( backlight ) SFX_FILE="${DESKTOP__SFX_PATH}/yaru-audio-volume-change.oga" ;; - ( gamedock ) SFX_FILE="${DESKTOP__SFX_PATH}/gamedock.oga" ;; - ( homedock ) SFX_FILE="${DESKTOP__SFX_PATH}/homedock.oga" ;; - ( login ) SFX_FILE="${DESKTOP__SFX_PATH}/yaru-desktop-login.oga" ;; - ( logout ) SFX_FILE="${DESKTOP__SFX_PATH}/smooth-desktop-login.oga" ;; - ( mute ) SFX_FILE="${DESKTOP__SFX_PATH}/smooth-dialog-warning.oga" ;; - ( notify ) SFX_FILE="${DESKTOP__SFX_PATH}/yaru-complete.oga" ;; - ( undock ) SFX_FILE="${DESKTOP__SFX_PATH}/yaru-desktop-login.oga" ;; - ( volume ) SFX_FILE="${DESKTOP__SFX_PATH}/yaru-message.oga" ;; - ( * ) - SFX_FILE="$1" - ;; - esac + # mapped names live in $DOTWRYN/config/sfx.map (shared with the hyprland quickshell) + local mapped_file="$(awk -v name="${1}" '!/^#/ && $1 == name { print $2; exit }' "${DOTWRYN}/config/sfx.map" 2>/dev/null)" + + [[ "${mapped_file}" ]] \ + && SFX_FILE="${DESKTOP__SFX_PATH}/${mapped_file}" \ + || SFX_FILE="${1}" \ + ; return 1 } diff --git a/scwrypts/remote/tmux/tmux.conf b/scwrypts/remote/tmux/tmux.conf index 289d944..8602bd1 100644 --- a/scwrypts/remote/tmux/tmux.conf +++ b/scwrypts/remote/tmux/tmux.conf @@ -3,6 +3,7 @@ set -g default-terminal "tmux-256color" set -g mouse off set -sg escape-time 0 set-option -g default-shell /bin/zsh +set-option -ga update-environment HYPRLAND_INSTANCE_SIGNATURE set-option -gw xterm-keys on set-window-option -g mode-keys vi set-window-option -g status-keys vi