Merge remote-tracking branch 'origin/main' into yolo
CI / lint (push) Successful in 32s
CI / eval (push) Successful in 2m28s

# Conflicts:
#	home/common/programs/default.nix
This commit is contained in:
naps62
2026-08-17 13:09:04 +00:00
9 changed files with 214 additions and 1 deletions
+1
View File
@@ -10,6 +10,7 @@
./zsh.nix
./nix.nix
./neovim
./editors.nix
./rust.nix
./elixir.nix
./nodejs.nix
+2
View File
@@ -57,6 +57,8 @@
];
pointerCursor = {
# setting the block alone no longer implies generation; must be explicit
enable = true;
package = pkgs.numix-cursor-theme;
name = "Numix-Cursor-Light";
};
+12
View File
@@ -0,0 +1,12 @@
{ pkgs, ... }:
{
# Trialling GUI editors alongside neovim, for reviewing agent-generated
# diffs. Drop the ones that don't stick.
home.packages = with pkgs; [
zed-editor
code-cursor
# VSCodium and Cursor both default to Open VSX, which carries
# GitHub.vscode-pull-request-github. Zed has no PR review at all.
vscodium
];
}
+2 -1
View File
@@ -1,6 +1,7 @@
{ pkgs, ... }:
{
home.packages = with pkgs; [
elixir_1_18
# top-level elixir_* aliases are deprecated in favour of the beamPackages sets
beamPackages.elixir_1_18
];
}
+65
View File
@@ -2,11 +2,49 @@
config,
lib,
pkgs,
inputs,
...
}:
let
cfg = config.custom.hyprland;
hyprPkgs = inputs.hyprland.packages.${pkgs.stdenv.hostPlatform.system};
# xdph runs this instead of hyprland-share-picker (screencopy:custom_picker_binary).
# The contract is just "print [SELECTION] to stdout"; a leading "r" grants a
# restore token. Printing it immediately means the dialog never renders.
#
# Why bypass the picker at all: Slack opens a burst of screencast sessions per
# share and only asks for persist_mode=1, so the leading sessions race ahead of
# their restore token and re-prompt. allow_token_by_default alone still left
# ~3 dialogs per share.
#
# Escape hatch: `touch ~/.config/hypr/share-picker-manual` to get the real
# picker back when you need a window or region instead of a whole screen.
#
# Deliberately NOT the focused monitor: at the moment you hit "share screen"
# the focus is on the Slack/Chrome window, which is generally not the screen
# you actually want to show. Auto-picking it would silently share the wrong
# display. shareOutput is pinned per-host instead.
sharePicker = pkgs.writeShellScriptBin "xdph-auto-picker" ''
realPicker="${hyprPkgs.xdg-desktop-portal-hyprland}/bin/hyprland-share-picker"
if [ -e "$HOME/.config/hypr/share-picker-manual" ]; then
exec "$realPicker" "$@"
fi
want=${lib.escapeShellArg (toString cfg.shareOutput)}
# Only auto-select if that output is actually connected right now; otherwise
# fall back to the picker rather than sharing something unexpected.
if ${hyprPkgs.hyprland}/bin/hyprctl monitors -j 2>/dev/null \
| ${pkgs.jq}/bin/jq -e --arg o "$want" 'any(.[]; .name == $o)' >/dev/null 2>&1; then
printf '[SELECTION]r/screen:%s\n' "$want"
else
exec "$realPicker" "$@"
fi
'';
# Floating webcam preview for the "corner-cam" recording trick: run `webcam`,
# then capture the whole screen with kooha/wf-recorder — the preview is in the
# recording. v4l2 only (won't work with the laptop's IPU6 cam). Optional device
@@ -32,10 +70,21 @@ in
default = 24;
description = "Cursor size for XCURSOR_SIZE and HYPRCURSOR_SIZE";
};
shareOutput = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
example = "HDMI-A-1";
description = ''
Output that screen-sharing auto-selects, bypassing the xdph picker
entirely. null keeps the normal picker. Falls back to the picker if
the named output isn't currently connected.
'';
};
};
imports = [
./cursor.nix
./kbptr.nix
./noctalia
./wallpapers.nix
];
@@ -62,6 +111,22 @@ in
WEBKIT_DISABLE_DMABUF_RENDERER = "1";
};
# xdph's share-picker is stateless: the "Allow a restore token" checkbox
# starts unticked, so no app ever receives a token and every single share
# re-prompts (journal showed 0 "Sent restore token" in 30 days, always
# re-selecting the same output). This pre-ticks it, so OBS/Chrome/Slack get
# a token on first share and skip the dialog thereafter.
# Note: xdph.conf is still hyprlang — the 0.55 Lua switch only hit the
# compositor config, not this separate binary.
xdg.configFile."hypr/xdph.conf".text = ''
screencopy {
allow_token_by_default = true
${lib.optionalString (
cfg.shareOutput != null
) " custom_picker_binary = ${sharePicker}/bin/xdph-auto-picker"}
}
'';
wayland.windowManager.hyprland = {
enable = true;
package = null;
+115
View File
@@ -0,0 +1,115 @@
{ pkgs, ... }:
let
# wl-kbptr's OpenCV target detection (the `detect` source used by SUPER+G) is
# hardcoded for a ~1080p screen and finds almost nothing on a 4K one.
#
# src/target_detection.cpp:264 discards any candidate target with
# `height >= 50 || width >= 500`. Every monitor here is 4K at scale = 1, so an
# ordinary button or list row is ~50px tall and well over 500px wide — i.e.
# basically every real control is thrown away for being "too big", and the
# only survivors are emoji and avatars. Verified on-screen: stock, a full
# Slack window got ~8 labels, all on images; patched, every channel, tab and
# button gets one.
#
# Doubling the four thresholds (and the dilate kernel that merges glyph edges
# into word blobs) is exactly the 1080p->4K ratio. None of these are exposed
# as config options in 0.4.1, so patching is the only way in. Drop this
# override if upstream makes detection DPI-aware.
# Second bug, on the rotated monitor (DP-2, transform = 1): the pointer lands
# in the wrong place because the rotation is applied twice.
#
# src/utils_wayland.c move_pointer() takes output->width/height — which come
# from `xdg_output.logical_size`, i.e. already rotated — and then runs
# _apply_transform() over them again before motion_absolute(). Hyprland's
# virtual-pointer also works in logical space, so the second rotation is pure
# error. Measured: asking for global (4000,1000) on DP-2 landed at
# (4286,3726); the double-rotation arithmetic predicts (4301,3736).
#
# Passing TRANSFORM_NORMAL makes the call a no-op while keeping the (static)
# function referenced, so no unused-function warning. Only correct for
# compositors that expect logical coords; fine here, Hyprland-only config.
wl-kbptr = pkgs.wl-kbptr.overrideAttrs (old: {
postPatch = (old.postPatch or "") + ''
substituteInPlace src/utils_wayland.c \
--replace-fail \
'&x, &y, &output_width, &output_height, state->current_output->transform' \
'&x, &y, &output_width, &output_height, WL_OUTPUT_TRANSFORM_NORMAL'
substituteInPlace src/target_detection.cpp \
--replace-fail \
'rect.height >= 50 || rect.width >= 500 || rect.height <= 3 ||' \
'rect.height >= 100 || rect.width >= 1000 || rect.height <= 6 ||' \
--replace-fail 'rect.width <= 7) {' 'rect.width <= 14) {' \
--replace-fail 'if (rect.height <= 6) {' 'if (rect.height <= 12) {' \
--replace-fail 'round(2.5 * scale), round(3.5 * scale)' \
'round(5.0 * scale), round(7.0 * scale)'
'';
});
in
{
home.packages = [ wl-kbptr ];
# wl-kbptr — move/click the pointer with the keyboard. We reorder the label
# alphabet so the easiest tiles land on the home row, set a monospace label
# font so labels stay legible over busy backgrounds, and roughly double every
# size default. Run `wl-kbptr --help-config` for the full option list.
#
# The size bump is not taste, it's DPI: every monitor here is 4K at
# `scale = 1` (see home/*/monitors.nix), and wl-kbptr paints raw cairo onto a
# layer surface, so it never sees a scale factor — GDK_SCALE only fixes GTK
# apps. Upstream's defaults are tuned for ~1080p and render half-size here.
#
# `label_font_size` is `min proportion max`, evaluated as
# clamp(area_height * proportion, min, max). In floating/detect mode the
# detected areas are buttons and icons, i.e. short, so it's the *minimum* that
# is in force for nearly every label — raising max alone would change nothing.
xdg.configFile."wl-kbptr/config".text = ''
[general]
modes=tile,bisect
[mode_tile]
label_symbols=asdfghjklqwertyuiopzxcvbnm
label_font_family=monospace
label_font_size=20 50% 120
[mode_floating]
label_symbols=asdfghjklqwertyuiopzxcvbnm
label_font_family=monospace
label_font_size=28 45% 80
[mode_bisect]
label_font_family=monospace
label_font_size=32
label_padding=20
pointer_size=32
[mode_split]
pointer_size=32
[mode_click]
button=left
'';
wayland.windowManager.hyprland.extraConfig = ''
-- wl-kbptr: keyboard pointer control.
--
-- SUPER+G "vimium for the desktop" OpenCV detects clickable regions
-- from a screencopy frame and labels them; type a label and it
-- moves the pointer to that region's centre and left-clicks.
-- Needs the HiDPI patch in kbptr.nix to find anything here.
-- SUPER+SHIFT+G tile -> bisect, for anywhere detection misses. Type the tile
-- label, then bisect with the home row; g/h/b left/right/middle
-- click, Enter/Space just parks the cursor, Backspace steps back.
--
-- Both need wlr-layer-shell + wlr-virtual-pointer (+ wlr-screencopy for
-- detect), all of which Hyprland implements.
-- spelled out rather than reusing the `mod` local from default.nix: both
-- chunks land in the same hyprland.lua but their relative order isn't pinned.
-- Guarded with `pidof` the same way hypridle's lock_cmd is. wl-kbptr only
-- exits on a keypress (main.c sets running=false solely from the keyboard
-- handler), so an overlay that loses focus before you type a label just sits
-- there holding an exclusive keyboard grab until it's killed from a shell
-- which happened here. The guard stops repeat presses stacking more of them.
hl.bind("SUPER + G", hl.dsp.exec_cmd("pidof wl-kbptr || wl-kbptr -o modes=floating,click -o mode_floating.source=detect"))
hl.bind("SUPER + SHIFT + G", hl.dsp.exec_cmd("pidof wl-kbptr || wl-kbptr"))
'';
}
+4
View File
@@ -78,6 +78,10 @@
fzf = {
enable = true;
enableZshIntegration = true;
# Both fzf and atuin bind Ctrl-R, and home-manager warns about the clash.
# Atuin is sourced last so it already won; this just makes that explicit
# and silences the warning. fzf keeps Ctrl-T and Alt-C.
historyWidget.zsh.command = "";
};
zoxide = {