Compare commits

...

10 Commits

Author SHA1 Message Date
eed3db9f05 home: relabel Vesktop as Discord and fix screenshare quality
Add vesktop (Discord+Vencord) with a proper Wayland/PipeWire screenshare
to replace the official discord client, whose Linux WebRTC path starved
the stream into macroblocks. A launcher-entry override + an icon-only
derivation (real Discord SVG, no runtime closure) relabel it to
"Discord" with the genuine logo. Drops the official discord package.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 00:54:28 +01:00
fde84f60ff home: illogical-impulse overrides, Firefox, Spicetify, share picker
The bulk of the home-manager config: re-applied Hyprland dots overrides
(UK layout, monitor layout, Qt=kde theme, foot/kitty Ctrl+C, cursor,
fish greeting), the themed hyprland-preview-share-picker + xdph wiring,
Firefox personal/work profiles, Spicetify, mimeapps, and the CLI/app
package set. Also wires up the Trayscale autostart and 1Password Firefox
overlay on the home side.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 00:54:15 +01:00
05a6d6cc69 hosts: set UK keymap and locale for TTY and greeter
console.keyMap = uk and xkb.layout = gb so the TTYs and SDDM greeter
match the UK layout (Hyprland's own layout is set in home).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 00:51:58 +01:00
ff8196fc77 hosts: enable 1Password (GUI + CLI) with Firefox integration
programs._1password-gui/_1password with polkit owner zephrynis, and
allowlist NixOS's .firefox-wrapped binary so the browser extension can
connect to the desktop app.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 00:51:58 +01:00
91053f9693 noise-suppression: RNNoise virtual mic via PipeWire, enable on host
App-independent mic denoising as a PipeWire filter-chain exposing a
"Noise Canceling source" — replaces Discord's unusable Krisp and works
in every app. Imported into the nixos-pc host.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 00:51:47 +01:00
4b3983d895 tailscale: add service + Trayscale, enable on host
Tailscale with --operator=zephrynis so the daemon is drivable without
sudo (Trayscale + tailscale switch/login from a plain shell), plus the
Trayscale systray app. Imported into the nixos-pc host.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 00:51:47 +01:00
3d8fec08e5 gaming: add Prism Launcher
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 00:51:35 +01:00
3a2145c076 desktop: autologin into Hyprland, Qt/KDE theming, dots fonts
Disable UWSM (black-screens under NVIDIA), autologin to the Hyprland
session, restore upstream qt platformTheme=kde + Darkly, and install the
fonts the end-4 dots actually reference (Google Sans Flex, Readex Pro,
Space Grotesk).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 00:51:35 +01:00
b57b5fea7c core: enable redistributable firmware for Realtek dongle
Pulls in the RTL8821CU WiFi/Bluetooth blobs (and other devices).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 00:51:35 +01:00
b90e17b6a6 flake: add share-picker, firefox-addons, google-sans-flex, spicetify inputs
Pin the new inputs the config now depends on (GTK4 screenshare picker,
rycee's firefox-addons tree, the Google Sans Flex font, and spicetify-nix)
and add flake.lock.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 00:51:35 +01:00
9 changed files with 873 additions and 4 deletions

249
flake.lock generated Normal file
View File

@@ -0,0 +1,249 @@
{
"nodes": {
"dotfiles": {
"flake": false,
"locked": {
"lastModified": 1778966846,
"narHash": "sha256-N7HUWD3dliyPDXA5qXhuma5segnnopgPo2nv5iFaeXc=",
"ref": "refs/heads/main",
"rev": "c0706258b1ff22c70f6c0e51c4b812e63703f620",
"revCount": 6244,
"submodules": true,
"type": "git",
"url": "https://github.com/end-4/dots-hyprland"
},
"original": {
"submodules": true,
"type": "git",
"url": "https://github.com/end-4/dots-hyprland"
}
},
"firefox-addons": {
"flake": false,
"locked": {
"lastModified": 1784402804,
"narHash": "sha256-NKjHErNuyO2x+YBUNOJy/8uKXfuMX60SLFuvZiMYdA8=",
"owner": "rycee",
"repo": "nur-expressions",
"rev": "775bd8e526823ab58efe5d1fc269f2a60c3de2c1",
"type": "gitlab"
},
"original": {
"owner": "rycee",
"repo": "nur-expressions",
"type": "gitlab"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": [
"illogical-flake",
"nur",
"nixpkgs"
]
},
"locked": {
"lastModified": 1733312601,
"narHash": "sha256-4pDvzqnegAfRkPwO3wmwBhVi/Sye1mzps0zHWYnP88c=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"google-sans-flex": {
"flake": false,
"locked": {
"lastModified": 1763459024,
"narHash": "sha256-HMAS0L/Tsqyl1xI16cyIzg9LEb6Dyq91JY4wqFQV9Vs=",
"owner": "end-4",
"repo": "google-sans-flex",
"rev": "251aa5abd30496368f634e54ce2a508fe5a2fdfa",
"type": "github"
},
"original": {
"owner": "end-4",
"repo": "google-sans-flex",
"type": "github"
}
},
"home-manager": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1784407317,
"narHash": "sha256-iZrxHToDJWnvt+5LGAtvuQMTy1NZYlNEKbKaVtBJNcc=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "39411a8e12a5526d992e65bc7e3dc9a4414d6713",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "home-manager",
"type": "github"
}
},
"hyprland-preview-share-picker": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1782052593,
"narHash": "sha256-XE6RD/4Mhw/ZRBj0v94kLOERElat5V+e/X0L9eUGf7M=",
"ref": "refs/heads/master",
"rev": "e2f30ff85486e557018523da45ccbc846e3a499c",
"revCount": 55,
"submodules": true,
"type": "git",
"url": "https://github.com/WhySoBad/hyprland-preview-share-picker"
},
"original": {
"submodules": true,
"type": "git",
"url": "https://github.com/WhySoBad/hyprland-preview-share-picker"
}
},
"illogical-flake": {
"inputs": {
"dotfiles": "dotfiles",
"nixpkgs": [
"nixpkgs"
],
"nur": "nur",
"quickshell": "quickshell"
},
"locked": {
"lastModified": 1779068758,
"narHash": "sha256-7osZ24TmUVDWZDxQ8Bi/P6GYiqTWw5azNWaVd+GfohY=",
"owner": "soymou",
"repo": "illogical-flake",
"rev": "8629ef302b956cf6ab0089338a867dbe844156fd",
"type": "github"
},
"original": {
"owner": "soymou",
"repo": "illogical-flake",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1784796856,
"narHash": "sha256-wWFrV5/Qbm+lyt5x20E/bSbfJiGKMo4RCxZV8cl/WZI=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "e2587caef70cea85dd97d7daab492899902dbf5d",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nur": {
"inputs": {
"flake-parts": "flake-parts",
"nixpkgs": [
"illogical-flake",
"nixpkgs"
]
},
"locked": {
"lastModified": 1779066378,
"narHash": "sha256-GqKhLVZTmZa68mLlCat57m54aeYPhrNAPVTLd2pGKYk=",
"owner": "nix-community",
"repo": "NUR",
"rev": "ab477ab3a75a85a68a80d9d26a572f9de9742808",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "NUR",
"type": "github"
}
},
"quickshell": {
"inputs": {
"nixpkgs": [
"illogical-flake",
"nixpkgs"
]
},
"locked": {
"lastModified": 1778488696,
"narHash": "sha256-QSWgYuZUCNUJ/cxmaq83WkcT7lHQDDfsPVgH+96kIl0=",
"ref": "refs/heads/master",
"rev": "7d1c9a9c6721606b129829134d6f614f015621e2",
"revCount": 818,
"type": "git",
"url": "https://git.outfoxxed.me/outfoxxed/quickshell"
},
"original": {
"type": "git",
"url": "https://git.outfoxxed.me/outfoxxed/quickshell"
}
},
"root": {
"inputs": {
"firefox-addons": "firefox-addons",
"google-sans-flex": "google-sans-flex",
"home-manager": "home-manager",
"hyprland-preview-share-picker": "hyprland-preview-share-picker",
"illogical-flake": "illogical-flake",
"nixpkgs": "nixpkgs",
"spicetify-nix": "spicetify-nix"
}
},
"spicetify-nix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"systems": "systems"
},
"locked": {
"lastModified": 1785048222,
"narHash": "sha256-LjFVfwxensz76SjnvofF1Jsw+xTLd8Qp31W0J6nh2XI=",
"owner": "Gerg-L",
"repo": "spicetify-nix",
"rev": "17e524f330c282d31c32dfe076222d4a12277886",
"type": "github"
},
"original": {
"owner": "Gerg-L",
"repo": "spicetify-nix",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

View File

@@ -17,6 +17,37 @@
# `dotfiles` input (this is the only source-override mechanism): # `dotfiles` input (this is the only source-override mechanism):
# inputs.dotfiles.url = "git+https://github.com/end-4/dots-hyprland?submodules=1&rev=<sha>"; # inputs.dotfiles.url = "git+https://github.com/end-4/dots-hyprland?submodules=1&rev=<sha>";
}; };
# GTK4 screenshare picker with live window/monitor previews — replaces the
# bare Qt hyprland-share-picker bundled with XDPH. Needs submodules.
hyprland-preview-share-picker = {
url = "git+https://github.com/WhySoBad/hyprland-preview-share-picker?submodules=1";
inputs.nixpkgs.follows = "nixpkgs";
};
# Declarative Firefox extensions (rycee's firefox-addons, the set behind
# nur.repos.rycee.firefox-addons — avoids importing all of NUR). Imported
# as a source tree via callPackage rather than its flake output so the
# addons evaluate under the system nixpkgs (needed for allowUnfree, e.g.
# the 1Password extension).
firefox-addons = {
url = "gitlab:rycee/nur-expressions";
flake = false;
};
# Main UI font of the current end-4 dots. Their installer clones this repo
# directly (the font is Google-proprietary, so no distro packages it).
google-sans-flex = {
url = "github:end-4/google-sans-flex";
flake = false;
};
# Declarative Spicetify (Spotify client mods). The home-manager module
# installs its own wrapped Spotify — pkgs.spotify must NOT be added anywhere.
spicetify-nix = {
url = "github:Gerg-L/spicetify-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
}; };
outputs = { nixpkgs, home-manager, ... }@inputs: { outputs = { nixpkgs, home-manager, ... }@inputs: {

View File

@@ -1,7 +1,38 @@
{ inputs, pkgs, ... }: { inputs, lib, pkgs, ... }:
let
share-picker = inputs.hyprland-preview-share-picker.packages.${pkgs.stdenv.hostPlatform.system}.default;
# Substitutes the current wallpaper palette (matugen's colors.json) into the
# picker stylesheet template on every launch, so the picker re-themes with
# the wallpaper, then hands off to the real picker binary.
share-picker-themed = pkgs.writeShellScript "share-picker-themed" ''
dir="$HOME/.config/hyprland-preview-share-picker"
colors="$HOME/.local/state/quickshell/user/generated/colors.json"
if [ -f "$colors" ]; then
${pkgs.jq}/bin/jq -r 'to_entries[] | "s|@\(.key)@|\(.value)|g"' "$colors" \
| ${pkgs.gnused}/bin/sed -f - "$dir/style.css.in" > "$dir/generated.css" || true
else
cp -f "$dir/style.css.in" "$dir/generated.css"
fi
exec ${share-picker}/bin/hyprland-preview-share-picker "$@"
'';
# Icon-only derivation: copies the real Discord logo (scalable SVG) out of the
# discord package under the theme name `discord`, so the relabeled Vesktop
# launcher entry below shows the genuine Discord logo. The output is the raw
# SVG bytes with no store references, so `discord` itself stays OUT of the
# runtime closure — it's fetched only at build time and freed by GC.
discord-icon = pkgs.runCommand "discord-icon" { } ''
install -Dm644 \
"${pkgs.discord}/opt/Discord/modules/discord_desktop_core/app/images/discord.svg" \
"$out/share/icons/hicolor/scalable/apps/discord.svg"
'';
in
{ {
imports = [ inputs.illogical-flake.homeManagerModules.default ]; imports = [
inputs.illogical-flake.homeManagerModules.default
inputs.spicetify-nix.homeManagerModules.spicetify
];
home.username = "zephrynis"; home.username = "zephrynis";
home.homeDirectory = "/home/zephrynis"; home.homeDirectory = "/home/zephrynis";
@@ -19,12 +50,366 @@
# hyprland.plugins = [ ]; # listOf package, loaded via hyprland's plugin mechanism # hyprland.plugins = [ ]; # listOf package, loaded via hyprland's plugin mechanism
}; };
# illogical-flake wipes and recopies ~/.config/hypr on every switch, so the
# UK layout override must be re-appended after its copy step each time.
# custom/general.lua is sourced after hyprland/general.lua (kb_layout = "us").
home.activation.ukKeyboardLayout = lib.hm.dag.entryAfter [ "copyIllogicalImpulseConfigs" ] ''
hyprCustomGeneral="$HOME/.config/hypr/custom/general.lua"
if [ -f "$hyprCustomGeneral" ] && ! grep -q 'kb_layout = "gb"' "$hyprCustomGeneral"; then
cat >> "$hyprCustomGeneral" << 'EOF'
-- UK keyboard layout (appended by nix-flake, overrides hyprland/general.lua)
hl.config({ input = { kb_layout = "gb" } })
EOF
echo "Appended UK keyboard layout to hypr/custom/general.lua"
fi
'';
# Suppress the Hyprland update news popup (option lives under ecosystem
# since Hyprland 0.46); re-appended after every recopy like the layout.
home.activation.hyprlandNoUpdateNews = lib.hm.dag.entryAfter [ "ukKeyboardLayout" ] ''
hyprCustomGeneral="$HOME/.config/hypr/custom/general.lua"
if [ -f "$hyprCustomGeneral" ] && ! grep -q 'no_update_news' "$hyprCustomGeneral"; then
cat >> "$hyprCustomGeneral" << 'EOF'
-- Suppress update news popup (appended by nix-flake)
hl.config({ ecosystem = { no_update_news = true } })
EOF
echo "Appended no_update_news to hypr/custom/general.lua"
fi
'';
# Monitor layout (captured from nwg-displays output, 2026-07-18): AOC
# CQ27G2S at 165Hz on the left, 4K TV at 1.5x scale to its right.
# Overrides the mode=preferred/position=auto catch-all in hyprland/general.lua.
home.activation.hyprlandMonitorLayout = lib.hm.dag.entryAfter [ "hyprlandNoUpdateNews" ] ''
hyprCustomGeneral="$HOME/.config/hypr/custom/general.lua"
if [ -f "$hyprCustomGeneral" ] && ! grep -q 'hl.monitor' "$hyprCustomGeneral"; then
cat >> "$hyprCustomGeneral" << 'EOF'
-- Monitor layout (appended by nix-flake, from nwg-displays 2026-07-18)
hl.monitor({ output = "DP-2", mode = "2560x1440@165.0", position = "0x0", scale = "1" })
hl.monitor({ output = "DP-1", mode = "3840x2160@59.98", position = "2560x678", scale = "1.5" })
-- Workspace 1 stays on the 4K TV; cursor starts on the 165Hz AOC
hl.workspace_rule({ workspace = "1", monitor = "DP-1", default = true })
hl.on("hyprland.start", function ()
hl.dispatch(hl.dsp.focus({ monitor = "DP-2" }))
hl.dispatch(hl.dsp.cursor.move({ x = 1280, y = 720 }))
end)
EOF
echo "Appended monitor layout to hypr/custom/general.lua"
fi
'';
# Tailscale tray icon in the bar's systray (daemon + package are system-side
# in modules/tailscale.nix). custom/execs.lua is the dots' intended autostart
# hook, but it's wiped and recopied every switch like the rest of ~/.config/hypr.
home.activation.trayscaleAutostart = lib.hm.dag.entryAfter [ "copyIllogicalImpulseConfigs" ] ''
hyprCustomExecs="$HOME/.config/hypr/custom/execs.lua"
if [ -f "$hyprCustomExecs" ] && ! grep -q 'trayscale' "$hyprCustomExecs"; then
cat >> "$hyprCustomExecs" << 'EOF'
-- Tailscale tray icon (appended by nix-flake)
hl.on("hyprland.start", function ()
hl.exec_cmd("trayscale --hide-window")
end)
EOF
echo "Appended trayscale autostart to hypr/custom/execs.lua"
fi
'';
# illogical-flake's generated custom/env.lua forces QT_QPA_PLATFORMTHEME=qt6ct
# (its workaround for plasma-integration not being installed). desktop.nix now
# installs plasma-integration + Darkly, so restore upstream's "kde" — that
# applies the matugen-generated kdeglobals (Material You colors, Darkly style)
# to Qt apps like Prism Launcher. Appended after the regeneration each switch;
# later hl.env calls win.
home.activation.qtPlatformThemeKde = lib.hm.dag.entryAfter [ "copyIllogicalImpulseConfigs" ] ''
hyprCustomEnv="$HOME/.config/hypr/custom/env.lua"
if [ -f "$hyprCustomEnv" ] && ! grep -q 'kde-platform-theme' "$hyprCustomEnv"; then
cat >> "$hyprCustomEnv" << 'EOF'
-- kde-platform-theme: restore upstream platform theme (appended by nix-flake);
-- plasma-integration + Darkly are installed system-side (desktop.nix)
hl.env("QT_QPA_PLATFORMTHEME", "kde")
EOF
echo "Restored QT_QPA_PLATFORMTHEME=kde in hypr/custom/env.lua"
fi
'';
# Same override for non-Hyprland entry points (illogical-flake's environment.nix
# sets this to qt6ct)
home.sessionVariables.QT_QPA_PLATFORMTHEME = lib.mkForce "kde";
# The illogical-flake copy step deletes/recreates ~/.config/hypr while
# Hyprland is running; its mid-copy reload fails ("cannot open hyprland.lua")
# and the error banner sticks. Reload once the configs are back in place.
home.activation.reloadHyprland = lib.hm.dag.entryAfter [ "hyprlandMonitorLayout" ] ''
for instance in /run/user/$(id -u)/hypr/*/; do
[ -d "$instance" ] || continue
HYPRLAND_INSTANCE_SIGNATURE="$(basename "$instance")" \
/run/current-system/sw/bin/hyprctl reload >/dev/null 2>&1 || true
done
'';
# Disable the desktop background clock widget. config.json is only copied on
# first install (user edits persist), so this mainly matters for fresh
# installs; the guard keeps later manual edits to other keys untouched.
home.activation.disableBackgroundClock = lib.hm.dag.entryAfter [ "copyIllogicalImpulseConfigs" ] ''
iiConfig="$HOME/.config/illogical-impulse/config.json"
if [ -f "$iiConfig" ] && [ "$(${pkgs.jq}/bin/jq '.background.widgets.clock.enable' "$iiConfig")" != "false" ]; then
${pkgs.jq}/bin/jq '.background.widgets.clock.enable = false' "$iiConfig" > "$iiConfig.tmp" \
&& mv "$iiConfig.tmp" "$iiConfig"
echo "Disabled background clock widget in illogical-impulse config.json"
fi
'';
# The dots swap terminal copy/interrupt conventions: foot binds Ctrl+C to
# clipboard-copy and moves SIGINT (\x03) to Ctrl+Shift+C; kitty maps Ctrl+C
# to copy_or_interrupt (copies whenever text is selected); foot also puts
# paste on Ctrl+V. Restore standard behavior — Ctrl+C interrupts, copy/paste
# on Ctrl+Shift+C/V — re-applied after every recopy like the hypr overrides.
home.activation.footCtrlC = lib.hm.dag.entryAfter [ "copyIllogicalImpulseConfigs" ] ''
footIni="$HOME/.config/foot/foot.ini"
if [ -f "$footIni" ] && grep -q '^clipboard-copy=Control+c$' "$footIni"; then
${pkgs.gnused}/bin/sed -i \
-e 's/^clipboard-copy=Control+c$/clipboard-copy=Control+Shift+c/' \
-e 's/^clipboard-paste=Control+v$/clipboard-paste=Control+Shift+v/' \
-e '/^\\x03=Control+Shift+c$/d' \
"$footIni"
echo "Restored default Ctrl+C/Ctrl+V in foot.ini"
fi
'';
home.activation.kittyCtrlC = lib.hm.dag.entryAfter [ "copyIllogicalImpulseConfigs" ] ''
kittyConf="$HOME/.config/kitty/kitty.conf"
if [ -f "$kittyConf" ] && ! grep -q 'Restore default Ctrl+C' "$kittyConf"; then
cat >> "$kittyConf" << 'EOF'
# Restore default Ctrl+C (appended by nix-flake); copy is Ctrl+Shift+C
map ctrl+c
EOF
echo "Unmapped Ctrl+C in kitty.conf"
fi
'';
# Point xdg-desktop-portal-hyprland at the preview share picker. xdph.conf
# lives in ~/.config/hypr, which the recopy wipes, so recreate it each switch.
home.activation.xdphSharePicker = lib.hm.dag.entryAfter [ "copyIllogicalImpulseConfigs" ] ''
cat > "$HOME/.config/hypr/xdph.conf" << 'EOF'
# Written by nix-flake (recreated every switch; the dots wipe this dir)
screencopy {
custom_picker_binary = ${share-picker-themed}
}
EOF
'';
# GTK apps decide light/dark from this key (via the settings portal). The
# dots' Arch installer sets it imperatively; the flake port never does, so
# GTK apps came up light while the shell rendered dark.
dconf.settings."org/gnome/desktop/interface".color-scheme = "prefer-dark";
# Share picker: single-click selection and Material-You styling. The colors
# reference GTK named colors from ~/.config/gtk-4.0/gtk.css, which matugen
# regenerates from the wallpaper — so the picker re-themes automatically.
# (This dir isn't shipped by the dots, so plain xdg.configFile works.)
xdg.configFile."hyprland-preview-share-picker/config.yaml".text = ''
stylesheets:
- generated.css
windows:
clicks: 1
outputs:
clicks: 1
'';
# Template for the wrapper above: @token@ placeholders are replaced with hex
# values from matugen's colors.json (Material tokens, current light/dark set).
xdg.configFile."hyprland-preview-share-picker/style.css.in".text = ''
.window {
background-color: @surface@;
color: @on_surface@;
border: 1px solid @outline_variant@;
border-radius: 24px;
padding: 10px;
font-family: "Google Sans Flex", "Rubik", sans-serif;
font-size: 15px;
}
.page,
.notebook,
.notebook stack {
background: transparent;
}
.notebook header {
background: transparent;
border: none;
margin-bottom: 6px;
}
.notebook header tab {
border-radius: 9999px;
padding: 6px 18px;
margin: 4px 3px;
background: transparent;
color: @on_surface_variant@;
transition: background-color 150ms ease;
outline: none;
box-shadow: none;
}
.notebook header tab:hover {
background-color: @surface_container_high@;
}
.notebook header tab:checked {
background-color: @primary_container@;
color: @on_primary_container@;
}
.card {
background-color: @surface_container@;
color: @on_surface@;
border: 2px solid transparent;
border-radius: 18px;
padding: 6px;
transition: background-color 150ms ease, border-color 150ms ease;
}
.card:hover {
background-color: @surface_container_high@;
border-color: @primary@;
}
.card-loading {
background-color: @surface_container@;
border-radius: 18px;
}
.image {
border-radius: 12px;
background: transparent;
}
.image-label {
color: @on_surface_variant@;
font-size: 0.92em;
margin-top: 4px;
}
/* The outputs view wraps each preview card in a classless GtkButton whose
stock background shows as a white matte neutralize it and let the
inner .card carry the visuals. */
.page button:not(.region-button) {
background: none;
border: none;
box-shadow: none;
padding: 0;
outline: none;
}
.page button:not(.region-button):hover,
.page button:not(.region-button):active {
background: none;
}
/* `background` shorthand, not background-color: the theme's
background-image gradient must be reset too. */
.region-button {
background: @primary_container@;
color: @on_primary_container@;
border: none;
border-radius: 9999px;
padding: 14px 32px;
font-size: 1.1em;
outline: none;
box-shadow: none;
}
.region-button:hover {
background: @secondary_container@;
}
.restore-button {
background-color: @surface_container@;
color: @on_surface_variant@;
border: none;
border-radius: 9999px;
padding: 6px 14px;
margin-top: 8px;
}
.restore-button check {
background-color: @surface_container_highest@;
border: 1px solid @outline@;
border-radius: 6px;
}
.restore-button check:checked {
background-color: @primary@;
color: @on_primary@;
border-color: @primary@;
}
'';
# The illogical-flake fish module generates its own config.fish, dropping the
# dots' `set fish_greeting` — so the "Welcome to fish" banner came back.
programs.fish.interactiveShellInit = "set -g fish_greeting";
programs.git = { programs.git = {
enable = true; enable = true;
userName = "zephrynis"; userName = "zephrynis";
userEmail = "zephrynis.yt@gmail.com"; userEmail = "zephrynis.yt@gmail.com";
}; };
# The dots run `hyprctl setcursor Bibata-Modern-Classic 24` but don't install
# the theme (an assumed system dependency on Arch)
home.pointerCursor = {
name = "Bibata-Modern-Classic";
package = pkgs.bibata-cursors;
size = 24;
gtk.enable = true;
x11.enable = true;
};
# Two profiles: personal (default, purple) and work (blue). Firefox has no
# native per-profile accent, so each profile tints its toolbars via
# userChrome.css. Launch work with `firefox -P work` (desktop entry below).
programs.firefox = {
enable = true;
profiles =
let
addons = pkgs.callPackage "${inputs.firefox-addons}/pkgs/firefox-addons" {
# Same wiring as the upstream flake's overlay, but against the system
# pkgs so nixpkgs.config (allowUnfree, for 1Password) applies
buildMozillaXpiAddon =
(import "${inputs.firefox-addons}/lib/mozilla.nix" { inherit lib; })
.mkBuildMozillaXpiAddon { inherit (pkgs) fetchurl stdenv; };
};
settings = {
# Load userChrome.css
"toolkit.legacyUserProfileCustomizations.stylesheets" = true;
# Auto-enable the declaratively installed extensions
"extensions.autoDisableScopes" = 0;
};
accent = color: ''
#navigator-toolbox {
background-color: ${color} !important;
color: #f4f2fa !important;
}
'';
in
{
personal = {
id = 0;
isDefault = true;
inherit settings;
extensions.packages = [ addons.ublock-origin addons.proton-pass ];
userChrome = accent "#45256e";
};
work = {
id = 1;
inherit settings;
extensions.packages = [ addons.ublock-origin addons.onepassword-password-manager ];
userChrome = accent "#1e3a6e";
};
};
};
# Launcher for the work profile; plain `firefox` opens personal (the default)
xdg.desktopEntries.firefox-work = {
name = "Firefox (Work)";
genericName = "Web Browser";
exec = "firefox -P work --name firefox-work %U";
icon = "firefox";
terminal = false;
categories = [ "Network" "WebBrowser" ];
};
programs.direnv = { programs.direnv = {
enable = true; enable = true;
nix-direnv.enable = true; nix-direnv.enable = true;
@@ -32,7 +417,65 @@
programs.vscode.enable = true; programs.vscode.enable = true;
# Spotify + Spicetify. The module installs its own wrapped Spotify, so
# pkgs.spotify is intentionally absent from home.packages. spicyLyrics is a
# first-class packaged extension (word-by-word karaoke lyrics view).
programs.spicetify =
let
spicePkgs = inputs.spicetify-nix.legacyPackages.${pkgs.stdenv.hostPlatform.system};
in
{
enable = true;
enabledExtensions = with spicePkgs.extensions; [
spicyLyrics
];
};
# File manager; gvfs/udisks2 backends are enabled system-side in desktop.nix.
# xdg-open and apps use this for directories. The claude-cli handler was
# registered imperatively by claude-code — kept here since HM now owns
# mimeapps.list.
# Relabel Vesktop's launcher entry to "Discord" with the real Discord logo.
# HM writes ~/.local/share/applications/vesktop.desktop, which shadows the
# package's own copy (user data dir wins in XDG_DATA_DIRS). Exec stays
# `vesktop`; StartupWMClass stays Vesktop so window matching still works.
xdg.desktopEntries.vesktop = {
name = "Discord";
genericName = "Internet Messenger";
exec = "vesktop %U";
icon = "discord";
categories = [ "Network" "InstantMessaging" "Chat" ];
mimeType = [ "x-scheme-handler/discord" ];
type = "Application";
settings = {
Keywords = "discord;vencord;electron;chat;";
StartupWMClass = "Vesktop";
};
};
xdg.mimeApps = {
enable = true;
defaultApplications = {
"inode/directory" = "org.gnome.Nautilus.desktop";
"x-scheme-handler/claude-cli" = "claude-code-url-handler.desktop";
};
};
home.packages = with pkgs; [ home.packages = with pkgs; [
# As a plain package, not programs.gh — the HM module symlinks a read-only
# config.yml into the store, which breaks `gh auth login`'s first-run write.
gh
nautilus
# Vesktop: Discord+Vencord with a real Wayland/PipeWire screenshare — a
# sane WebRTC bitrate (the official `discord` client starved it into
# macroblocks) and it streams desktop audio, which official Linux Discord
# can't. Uses the same xdg-desktop-portal-hyprland picker wired up above.
# Relabeled to "Discord" with the real Discord logo via the desktop-entry
# override + discord-icon below.
vesktop
discord-icon
slack
claude-code
ripgrep ripgrep
fd fd
fzf fzf

View File

@@ -7,6 +7,8 @@
../../modules/desktop.nix ../../modules/desktop.nix
../../modules/nvidia.nix ../../modules/nvidia.nix
../../modules/gaming.nix ../../modules/gaming.nix
../../modules/tailscale.nix
../../modules/noise-suppression.nix
]; ];
networking.hostName = "nixos-pc"; networking.hostName = "nixos-pc";
@@ -31,8 +33,25 @@
}; };
programs.fish.enable = true; programs.fish.enable = true;
# 1Password desktop app + CLI. The module (vs a plain package) sets up the
# polkit policy for system-auth unlock and the setgid onepassword group the
# browser integration needs.
programs._1password-gui = {
enable = true;
polkitPolicyOwners = [ "zephrynis" ];
};
programs._1password.enable = true;
# 1Password verifies the browser binary's name before connecting the
# extension to the app; NixOS's wrapped Firefox isn't on its allowlist
environment.etc."1password/custom_allowed_browsers" = {
text = ".firefox-wrapped";
mode = "0755";
};
time.timeZone = "Europe/London"; time.timeZone = "Europe/London";
i18n.defaultLocale = "en_GB.UTF-8"; i18n.defaultLocale = "en_GB.UTF-8";
console.keyMap = "uk"; # TTYs
services.xserver.xkb.layout = "gb"; # SDDM greeter / X11; Hyprland is set in home/zephrynis.nix
# Set to the NixOS release of the ISO used at install time; never change afterwards. # Set to the NixOS release of the ISO used at install time; never change afterwards.
system.stateVersion = "26.05"; system.stateVersion = "26.05";

View File

@@ -16,6 +16,9 @@
networking.networkmanager.enable = true; # required by illogical-flake networking.networkmanager.enable = true; # required by illogical-flake
# Firmware blobs for Realtek RTL8821CU WiFi/Bluetooth dongle (and other devices)
hardware.enableRedistributableFirmware = true;
hardware.bluetooth = { hardware.bluetooth = {
enable = true; enable = true;
powerOnBoot = true; powerOnBoot = true;

View File

@@ -1,8 +1,11 @@
{ pkgs, ... }: { inputs, pkgs, ... }:
{ {
# System-side requirements of illogical-flake (the HM module does the rest) # System-side requirements of illogical-flake (the HM module does the rest)
programs.hyprland.enable = true; programs.hyprland = {
enable = true;
withUWSM = false; # UWSM session black-screens (NVIDIA env vars + non-UWSM-aware dots)
};
services.geoclue2.enable = true; # QtPositioning — weather / night light widgets services.geoclue2.enable = true; # QtPositioning — weather / night light widgets
services.displayManager.sddm = { services.displayManager.sddm = {
@@ -10,8 +13,29 @@
wayland.enable = true; wayland.enable = true;
}; };
# Boot straight into Hyprland; the illogical-impulse lock screen acts as the
# login page (lock.launchOnStartup in ~/.config/illogical-impulse/config.json)
services.displayManager.autoLogin = {
enable = true;
user = "zephrynis";
};
services.displayManager.defaultSession = "hyprland";
xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-gtk ]; # file pickers etc. xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-gtk ]; # file pickers etc.
# Qt app theming (Prism Launcher, protonup-qt, …), matching upstream Arch:
# the dots set QT_QPA_PLATFORMTHEME=kde and write wallpaper Material You
# colors + widgetStyle=Darkly into ~/.config/kdeglobals, assuming
# plasma-integration and Darkly are system-installed (like Bibata). The
# illogical-flake port overrides the platform theme to qt6ct in
# hypr/custom/env.lua — countered in home/zephrynis.nix. No qt.style here:
# QT_STYLE_OVERRIDE would beat kdeglobals' widgetStyle.
qt = {
enable = true;
platformTheme = "kde";
};
environment.systemPackages = [ pkgs.darkly ]; # Qt6 only; no qt5 variant in nixpkgs
security.polkit.enable = true; security.polkit.enable = true;
programs.dconf.enable = true; programs.dconf.enable = true;
services.gvfs.enable = true; services.gvfs.enable = true;
@@ -19,6 +43,16 @@
services.upower.enable = true; services.upower.enable = true;
fonts.packages = with pkgs; [ fonts.packages = with pkgs; [
# Fonts the current dots actually use (config.json appearance.fonts):
# main/title/numbers = Google Sans Flex, reading = Readex Pro,
# expressive = Space Grotesk. Without these everything silently falls
# back to DejaVu Sans.
(runCommand "google-sans-flex-font" { } ''
install -Dm644 ${inputs.google-sans-flex}/*.ttf -t $out/share/fonts/truetype
'')
readexpro
(google-fonts.override { fonts = [ "SpaceGrotesk" ]; })
# named by the illogical-flake README # named by the illogical-flake README
rubik rubik
nerd-fonts.ubuntu nerd-fonts.ubuntu

View File

@@ -14,6 +14,7 @@
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
mangohud mangohud
prismlauncher
protonup-qt protonup-qt
]; ];
} }

View File

@@ -0,0 +1,74 @@
{ pkgs, ... }:
# App-independent microphone noise suppression via PipeWire + RNNoise.
#
# Replaces Discord's Krisp (which can't be enabled on current nixpkgs Discord:
# the binary is patchelf'd, and even after bypassing Krisp's signature check the
# newer "THz SDK" stage fails with KRISP_INIT_ERROR_GLOBAL_INIT). Instead we run
# RNNoise as a PipeWire filter-chain that exposes a cleaned virtual microphone.
#
# This works in EVERY app (Discord, browser, OBS, games), survives Discord
# updates, and touches no DRM. In each app's input settings, pick the source
# named "Noise Canceling source" instead of the raw mic.
{
# LADSPA plugin providing noise_suppressor_mono/stereo (werman RNNoise).
environment.systemPackages = [ pkgs.rnnoise-plugin ];
services.pipewire.extraConfig.pipewire."99-input-denoising" = {
"context.modules" = [
{
name = "libpipewire-module-filter-chain";
args = {
"node.description" = "Noise Canceling source";
"media.name" = "Noise Canceling source";
"filter.graph" = {
nodes = [
{
type = "ladspa";
name = "rnnoise";
plugin = "${pkgs.rnnoise-plugin}/lib/ladspa/librnnoise_ladspa.so";
label = "noise_suppressor_mono";
control = {
# Higher threshold = more aggressive gating of non-voice.
# 50% is a balanced starting point; raise toward 9095 if a
# noisy background still leaks through, lower if speech clips.
"VAD Threshold (%)" = 50.0;
"VAD Grace Period (ms)" = 200.0;
"Retroactive VAD Grace (ms)" = 0.0;
};
}
{
# Makeup gain baked into the graph so the source volume is
# reproducible instead of living only in WirePlumber's runtime
# state. Mult = 1.53 matches the 153% level set in the mixer.
type = "builtin";
name = "gain";
label = "linear";
control = {
"Mult" = 1.53;
"Add" = 0.0;
};
}
];
links = [
{ output = "rnnoise:Output"; input = "gain:In"; }
];
};
# The virtual mic apps select. media.class = Audio/Source makes it
# appear as a capture device throughout the desktop.
"capture.props" = {
"node.name" = "capture.rnnoise_source";
"node.passive" = true;
"audio.rate" = 48000;
};
"playback.props" = {
"node.name" = "rnnoise_source";
"media.class" = "Audio/Source";
"audio.rate" = 48000;
};
};
}
];
};
}

15
modules/tailscale.nix Normal file
View File

@@ -0,0 +1,15 @@
{ pkgs, ... }:
{
services.tailscale = {
enable = true;
# Let zephrynis drive the daemon without sudo — required for Trayscale,
# and makes `tailscale switch` / `login` work from a plain shell too
extraSetFlags = [ "--operator=zephrynis" ];
};
# Tray app for the bar's systray: status toggle + account switcher
# (work/personal via Tailscale fast user switching). Autostarted from
# hypr/custom/execs.lua (appended in home/zephrynis.nix).
environment.systemPackages = [ pkgs.trayscale ];
}