Rebase to flake parts #8

This commit is contained in:
2026-05-08 21:48:22 -03:00
parent f98606dcce
commit 34b89af77f
30 changed files with 3567 additions and 1 deletions
+14
View File
@@ -0,0 +1,14 @@
{ self, inputs, ... }: {
flake.nixosModules.desktop = {
imports = [
self.nixosModules.desktopShellDmsOptions
self.nixosModules.desktopHyprland
self.nixosModules.desktopNiri
self.nixosModules.desktopPlasma
self.nixosModules.desktopOptions
self.nixosModules.desktopGui
self.nixosModules.desktopWallpapers
self.nixosModules.desktopWaydroid
];
};
}
+128
View File
@@ -0,0 +1,128 @@
{ ... }: {
flake.nixosModules.desktopGui =
{ config, lib, pkgs, self, inputs, options, ... }:
let
pi5Greeter = self.lib.pi5NiriKdl;
cfg = config.chiasson.desktop;
guiEnabled = cfg.hyprland.enable || cfg.niri.enable || cfg.plasma.enable;
hmAvailable = lib.hasAttrByPath [ "home-manager" "sharedModules" ] options;
dm = cfg.displayManager;
variant = dm.variant or "sddm";
useGreeter = variant == "dankgreeter";
sddmTheme =
if dm.sddm.theme.package == null then
"breeze"
else
"${dm.sddm.theme.package}/share/sddm/themes/${dm.sddm.theme.id}";
effectiveDefaultSession =
if cfg.defaultSession != null then
cfg.defaultSession
else if cfg.hyprland.enable then
"hyprland"
else if cfg.niri.enable then
"niri"
else
"plasma";
greeterCompositor =
if effectiveDefaultSession == "niri" then
"niri"
else if effectiveDefaultSession == "hyprland" then
"hyprland"
else
"niri";
enabledUsers = config.chiasson.users.enabled or [ ];
greeterConfigHome =
if dm.greeter.configHome != null then
dm.greeter.configHome
else if enabledUsers != [ ] then
"/home/${builtins.head enabledUsers}"
else
null;
in
{
# Unconditional imports only — conditional imports that peek at `config` recurse during merge.
imports = [
inputs.dms.nixosModules.greeter
];
config = lib.mkMerge [
(lib.mkIf guiEnabled {
services.xserver.enable = true;
# Chromium/Electron (Edge, Vesktop, etc.) only add native Wayland + IME flags when this is
# set; nixpkgs wrappers gate on NIXOS_OZONE_WL and WAYLAND_DISPLAY. Helps PipeWire/desktop
# portal screen capture on wlroots-like sessions. Harmless on X11 (flags stay off).
environment.sessionVariables.NIXOS_OZONE_WL = lib.mkDefault "1";
xdg.portal.enable = true;
xdg.portal.extraPortals =
[ pkgs.xdg-desktop-portal-gtk ]
++ lib.optionals cfg.plasma.enable [ pkgs.kdePackages.xdg-desktop-portal-kde ];
})
(lib.mkIf (guiEnabled && !useGreeter) {
services.displayManager.sddm = {
enable = true;
wayland.enable = dm.sddm.wayland.enable;
theme = sddmTheme;
inherit (dm.sddm) enableHidpi settings;
extraPackages = lib.optionals (dm.sddm.theme.package != null) (
with pkgs.kdePackages; [
qtdeclarative
qtsvg
]
);
};
services.displayManager.defaultSession = effectiveDefaultSession;
})
(lib.mkIf (guiEnabled && useGreeter) {
assertions = [
{
assertion = greeterConfigHome != null;
message = "DankGreeter needs chiasson.desktop.displayManager.greeter.configHome or a non-empty chiasson.users.enabled list.";
}
];
services.displayManager.defaultSession = effectiveDefaultSession;
programs.dank-material-shell.greeter = {
enable = true;
compositor = {
name = greeterCompositor;
}
// lib.optionalAttrs (cfg.niri.raspberryPi5DrmWorkaround && greeterCompositor == "niri") {
customConfig = lib.mkDefault pi5Greeter.dankGreeterCompositorConfig;
};
configHome = greeterConfigHome;
};
})
(lib.mkIf (cfg.defaultPackages.enabled && guiEnabled) {
environment.systemPackages = cfg.defaultPackages.packages;
})
(lib.mkIf (cfg.extraPackages != [ ] && guiEnabled) {
environment.systemPackages = cfg.extraPackages;
})
(lib.mkIf (guiEnabled && cfg.keyring.enable) {
services.gnome.gnome-keyring.enable = true;
security.pam.services.login.enableGnomeKeyring = true;
services.xserver.updateDbusEnvironment = lib.mkDefault true;
# Electron apps (Element, Slack, etc.) use libsecret via keytar; without it they report
# "unsupported keyring" even if gnome-keyring is enabled.
environment.systemPackages = [ pkgs.libsecret ];
})
(lib.mkIf (guiEnabled && cfg.keyring.enable && !useGreeter) {
security.pam.services.sddm.enableGnomeKeyring = true;
})
(lib.mkIf (guiEnabled && cfg.keyring.enable && useGreeter) {
security.pam.services.greetd.enableGnomeKeyring = true;
})
(lib.mkIf (guiEnabled && cfg.keyring.enable && hmAvailable) {
"home-manager".sharedModules = [
({ lib, pkgs, ... }: {
services.gnome-keyring.enable = lib.mkDefault true;
home.packages = [ pkgs.gcr ];
})
];
})
];
};
}
+213
View File
@@ -0,0 +1,213 @@
{ self, ... }: {
flake.nixosModules.desktopHyprland =
{ config, options, lib, pkgs, ... }:
let
cfg = config.chiasson.desktop;
hmAvailable = lib.hasAttrByPath [ "home-manager" "sharedModules" ] options;
in
{
options.chiasson.desktop.hyprland = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Hyprland session + HM wiring.";
};
settings = lib.mkOption {
type = lib.types.attrs;
default = { };
description = "Extra `wayland.windowManager.hyprland.settings` merged with defaults.";
};
};
config = lib.mkMerge [
(lib.mkIf cfg.hyprland.enable {
programs.hyprland.enable = true;
})
(lib.mkIf (cfg.hyprland.enable && hmAvailable) {
"home-manager".sharedModules = [ self.homeManagerModules.desktopHyprland ];
})
];
};
flake.homeManagerModules.desktopHyprland = {
config,
lib,
osConfig ? { },
pkgs,
...
}:
let
hyprlandEnabled = osConfig.chiasson.desktop.hyprland.enable or false;
# nixpkgs hyprland-plugins pin is stale for current Hyprland — override to a known-good rev.
hyprbarsPatched =
let
hyprlandPluginsSrc = pkgs.fetchFromGitHub {
owner = "hyprwm";
repo = "hyprland-plugins";
rev = "b85a56b9531013c79f2f3846fd6ee2ff014b8960";
hash = "sha256-xwNa+1D8WPsDnJtUofDrtyDCZKZotbUymzV/R5s+M0I=";
};
in
pkgs.hyprlandPlugins.hyprbars.overrideAttrs (_: {
version = "unstable-2026-02-23";
src = "${hyprlandPluginsSrc}/hyprbars";
});
in
{
config = lib.mkIf hyprlandEnabled {
wayland.windowManager.hyprland = {
enable = true;
# null = use NixOS Hyprland/xdg-desktop-portal-hyprland (same versions as the session).
package = null;
portalPackage = null;
plugins = [ hyprbarsPatched ];
extraConfig = ''
source = ~/.config/hypr/colors.conf
'';
settings = lib.mkMerge [
{
monitor = [ ",preferred,auto,auto" ];
general = {
gaps_in = 8;
gaps_out = 4;
border_size = 2;
allow_tearing = false;
};
cursor.no_hardware_cursors = true;
env = [
"XCURSOR_THEME,phinger-cursors-dark"
"XCURSOR_SIZE,32"
];
input = {
kb_layout = "ca";
kb_variant = "";
numlock_by_default = true;
};
binds.scroll_event_delay = 50;
exec-once = [
"nm-applet --indicator &"
"sleep 1 && hyprctl reload"
];
# Default keybinds
bind =
[
"SUPER,T,exec,kitty -e fish"
"ControlSuper,T,exec,konsole"
"SUPER,D,exec,rofi -show drun"
"ControlSuper,D,exec,rofi -show window"
"SUPER,E,exec,dolphin"
"Super, Q, killactive"
"ControlSuper, Q, exec, hyprctl kill"
"Super, F, fullscreen, 0"
"Super, G, fullscreen, 1"
"ShiftSuper, F, fullscreenstate, 0 3"
"Super, Minus, splitratio, -0.1"
"Super, Equal, splitratio, 0.1"
"AltSuper, Space, togglefloating"
"Super, P, pin"
"Super, Space, exec, dms ipc call spotlight toggle"
"Super, I, exec, dms ipc call settings focusOrToggle"
"Super, N, exec, dms ipc call notepad toggle"
"ShiftSuper, N, exec, dms ipc call notifications toggle"
"Super, M, exec, dms ipc call processlist focusOrToggle"
"Super, L, exec, dms ipc call lock lock"
"ShiftSuper, V, exec, dms ipc call clipboard toggle"
"Super, Tab, cyclenext"
"Super, Tab, bringactivetotop"
"Super, left, movefocus, l"
"Super, right, movefocus, r"
"Super, up, movefocus, u"
"Super, down, movefocus, d"
"ShiftSuper, left, movewindow, l"
"ShiftSuper, right, movewindow, r"
"ShiftSuper, up, movewindow, u"
"ShiftSuper, down, movewindow, d"
]
++ (builtins.map (i: "Super, ${toString i}, workspace, ${toString i}") (builtins.genList (n: n + 1) 9))
++ (builtins.map (i: "ControlSuper, ${toString i}, focusworkspaceoncurrentmonitor, ${toString i}") (builtins.genList (n: n + 1) 9))
++ (builtins.map (i: "ShiftSuper, ${toString i}, movetoworkspacesilent, ${toString i}") (builtins.genList (n: n + 1) 9));
bindm = [
" , mouse:282, movewindow"
"Super, mouse:272, movewindow"
"Super, mouse:273, resizewindow"
];
bindl = [
"Super, mouse_up, splitratio, -0.1"
"Super, mouse_down, splitratio, 0.1"
" , XF86AudioPlay, exec, playerctl play-pause"
" , XF86AudioPrev, exec, playerctl previous"
" , XF86AudioNext, exec, playerctl next"
" , XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"
" , XF86MonBrightnessDown, exec, brightnessctl s 10%-"
" , XF86MonBrightnessUp, exec, brightnessctl s +10%"
];
bindel = [
" , XF86AudioRaiseVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+"
" , XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-"
];
# Decoration / blur
decoration = {
rounding = 10;
active_opacity = 0.95;
inactive_opacity = 0.85;
shadow = {
enabled = true;
range = 5;
render_power = 8;
};
blur = {
enabled = true;
new_optimizations = true;
xray = false;
size = 2;
passes = 4;
vibrancy = 10;
};
};
misc = {
disable_hyprland_logo = true;
disable_splash_rendering = true;
};
plugin.hyprbars = {
enabled = true;
bar_height = 30;
bar_blur = true;
bar_padding = 10;
bar_button_padding = 7;
bar_precedence_over_border = true;
bar_part_of_window = true;
bar_title_enabled = true;
bar_text_size = 10;
bar_text_font = "Sans";
bar_text_align = "center";
bar_buttons_alignment = "left";
icon_on_hover = true;
"hyprbars-button" = [
"rgb(ed6a5f), 12, 󰖭, hyprctl dispatch killactive, rgb(460804)"
"rgb(f6be50), 12, 󰖰, hyprctl dispatch movetoworkspacesilent special:minimized, rgb(90591d)"
"rgb(61c555), 12, 󰘖, hyprctl dispatch fullscreen 1, rgb(2a6218)"
];
};
windowrule = [
"sync_fullscreen 0, match:class ^(?i)microsoft-edge|Spotify|org.kde.gwenview|zen-beta$"
"opacity 1.0 override 0.95 override, match:class ^(?i)microsoft-edge$"
"opacity 1.0 override 1.00 override, match:class ^(?i)com.stremio.stremio$"
"opacity 1.0 override 0.85 override, match:class ^(?i)zen-beta$"
"no_screen_share on, match:class ^(?i)(microsoft-edge|zen-beta)$, match:title ^(?i).*(scotiabank|paypal).*"
"no_screen_share on, match:class ^(?i)microsoft-edge$, match:initial_title ^(?i).*(?i)personal 2.*edge.*$"
"hyprbars:no_bar on, match:class ^(?i)(microsoft-edge|Cursor|Flow|looking-glass-client|localsend_app)$"
];
}
(osConfig.chiasson.desktop.hyprland.settings or { })
];
};
};
};
}
+207
View File
@@ -0,0 +1,207 @@
{ self, inputs, ... }:
let
# Keep defaults in this let — a bare niri-settings.nix next to this file would get picked up by import-tree.
niriBaseSettings =
pkgs:
{
input.keyboard = {
xkb.layout = "ca";
xkb.variant = "";
};
input."focus-follows-mouse" = _: {
props."max-scroll-amount" = "45%";
content = { };
};
input."warp-mouse-to-focus" = _: { };
layout.gaps = 5;
window-rules = [
{
matches = [
{
app-id = "^$";
title = "^$";
}
];
open-floating = true;
open-focused = false;
}
];
#TODO[epic=Binds] Go over binds again
binds = {
"Mod+T"."spawn-sh" = "${pkgs.lib.getExe pkgs.kitty} -e fish"; #TODO[epic=Binds] This should only be set if having kitty
"Mod+Control+T"."spawn-sh" = "konsole"; #TODO[epic=Binds] This should only be set if having konsole
"Mod+D"."spawn-sh" = "rofi -show drun"; #TODO[epic=Binds] This should only be set if having rofi
"Mod+Space"."spawn-sh" = "dms ipc call spotlight toggle"; #TODO[epic=Binds] This should only be set if having dms
"Mod+E"."spawn-sh" = "dolphin"; #TODO[epic=Binds] This should only be set if having dolphin
"Mod+Control+O"."spawn-sh" = "rofi -show window"; #TODO[epic=Binds] This should only be set if having rofi
"Mod+V"."spawn-sh" = "dms ipc call clipboard toggle"; #TODO[epic=Binds] This should only be set if having dms
"Mod+N"."spawn-sh" = "dms ipc call notepad toggle"; #TODO[epic=Binds] This should only be set if having dms
"Mod+Shift+N"."spawn-sh" = "dms ipc call notifications toggle"; #TODO[epic=Binds] This should only be set if having dms
"Mod+M"."spawn-sh" = "dms ipc call processlist toggle"; #TODO[epic=Binds] This should only be set if having dms
"Mod+L"."spawn-sh" = "dms ipc call lock lock"; #TODO[epic=Binds] This should only be set if having dms
"Mod+Q"."close-window" = _: { };
"Mod+F"."maximize-column" = _: { };
"Mod+Shift+F"."fullscreen-window" = _: { };
"Mod+O"."toggle-overview" = _: { };
"Mod+Shift+NumberSign"."show-hotkey-overlay" = _: { };
"Mod+Shift+E".quit = _: { };
"Mod+Left"."focus-column-or-monitor-left" = _: { };
"Mod+Down"."focus-window-or-monitor-down" = _: { };
"Mod+Up"."focus-window-or-monitor-up" = _: { };
"Mod+Right"."focus-column-or-monitor-right" = _: { };
"Mod+Shift+WheelScrollDown"."focus-workspace-down" = _: { };
"Mod+Shift+WheelScrollUp"."focus-workspace-up" = _: { };
"Mod+WheelScrollDown"."focus-column-or-monitor-right" = _: { };
"Mod+WheelScrollUp"."focus-column-or-monitor-left" = _: { };
"Mod+Shift+Left"."move-column-left-or-to-monitor-left" = _: { };
"Mod+Shift+Down"."move-window-down" = _: { };
"Mod+Shift+Up"."move-window-up" = _: { };
"Mod+Shift+Right"."move-column-right-or-to-monitor-right" = _: { };
"Mod+Page_Up"."focus-workspace-up" = _: { };
"Mod+Page_Down"."focus-workspace-down" = _: { };
"Mod+Shift+Page_Up"."move-column-to-workspace-up" = _: { };
"Mod+Shift+Page_Down"."move-column-to-workspace-down" = _: { };
"Mod+R"."switch-preset-column-width" = _: { };
"Mod+BracketLeft"."consume-or-expel-window-left" = _: { };
"Mod+BracketRight"."consume-or-expel-window-right" = _: { };
"Mod+Comma"."consume-window-into-column" = _: { };
"Mod+Period"."expel-window-from-column" = _: { };
"Mod+Alt+Space"."toggle-window-floating" = _: { };
#"Mod+Shift+V"."switch-focus-between-floating-and-tiling" = _: { }; #TODO[epic=Binds] Find free bind that is not in the way and not overcomplicated to remember
"Mod+1"."focus-workspace" = 1;
"Mod+2"."focus-workspace" = 2;
"Mod+3"."focus-workspace" = 3;
"Mod+4"."focus-workspace" = 4;
"Mod+5"."focus-workspace" = 5;
"Mod+6"."focus-workspace" = 6;
"Mod+7"."focus-workspace" = 7;
"Mod+8"."focus-workspace" = 8;
"Mod+9"."focus-workspace" = 9;
"Mod+Ctrl+1"."move-column-to-workspace" = 1;
"Mod+Ctrl+2"."move-column-to-workspace" = 2;
"Mod+Ctrl+3"."move-column-to-workspace" = 3;
"Mod+Ctrl+4"."move-column-to-workspace" = 4;
"Mod+Ctrl+5"."move-column-to-workspace" = 5;
"Mod+Ctrl+6"."move-column-to-workspace" = 6;
"Mod+Ctrl+7"."move-column-to-workspace" = 7;
"Mod+Ctrl+8"."move-column-to-workspace" = 8;
"Mod+Ctrl+9"."move-column-to-workspace" = 9;
"XF86AudioRaiseVolume".spawn = [
"wpctl"
"set-volume"
"@DEFAULT_AUDIO_SINK@"
"0.05+"
];
"XF86AudioLowerVolume".spawn = [
"wpctl"
"set-volume"
"@DEFAULT_AUDIO_SINK@"
"0.05-"
];
"XF86AudioMute".spawn = [
"wpctl"
"set-mute"
"@DEFAULT_AUDIO_SINK@"
"toggle"
];
Print.screenshot = _: { };
"Ctrl+Print"."screenshot-screen" = _: { };
"Alt+Print"."screenshot-window" = _: { };
};
};
mergeNiriSettings =
pkgs: niriCfg:
let
lib = pkgs.lib;
pi5 = self.lib.pi5NiriKdl;
rpi5Extra = lib.optionalString (niriCfg.raspberryPi5DrmWorkaround or false) pi5.drmExtraConfig;
userExtra = niriCfg.extraSettings or { };
extraConfigMerged = rpi5Extra + (userExtra.extraConfig or "");
in
lib.recursiveUpdate (niriBaseSettings pkgs) (
userExtra
// lib.optionalAttrs (rpi5Extra != "" || (userExtra.extraConfig or "") != "") {
extraConfig = extraConfigMerged;
}
);
in
{
flake.homeManagerModules.desktopNiri =
{ lib, pkgs, osConfig ? { }, ... }:
let
niriOs = osConfig.chiasson.desktop.niri or { };
niriEnabled = osConfig.chiasson.desktop.niri.enable or false;
mergedSettings = mergeNiriSettings pkgs niriOs;
niriConfigPkg = inputs.wrapper-modules.wrappers.niri.wrap {
inherit pkgs;
settings = mergedSettings;
};
in
{
config = lib.mkIf niriEnabled {
xdg.configFile."niri/config.kdl".source = "${niriConfigPkg}/niri-config.kdl";
};
};
flake.nixosModules.desktopNiri =
{ config, options, lib, pkgs, self, ... }:
let
cfg = config.chiasson.desktop;
hmAvailable = lib.hasAttrByPath [ "home-manager" "sharedModules" ] options;
in
{
options.chiasson.desktop.niri = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Niri compositor session + NixOS packages.";
};
raspberryPi5DrmWorkaround = lib.mkEnableOption ''
Pi 5 + RP1 DSI: Niri DRM debug rules (renderD128, ignore card1/2) + matching DankGreeter niri config.
Opt-in only HDMI-only / other Pi setups do not need this.
'';
extraSettings = lib.mkOption {
type = lib.types.attrs;
default = { };
description = ''
Merged into shared defaults `config.kdl` via wrapper-modules. Put raw KDL in `extraConfig`.
'';
};
};
config = lib.mkMerge [
{
assertions = [
{
assertion = !cfg.niri.raspberryPi5DrmWorkaround || cfg.niri.enable;
message = "chiasson.desktop.niri.raspberryPi5DrmWorkaround requires chiasson.desktop.niri.enable.";
}
];
}
(lib.mkIf cfg.niri.enable {
programs.niri.enable = true;
programs.niri.package = pkgs.niri;
programs.xwayland.enable = true;
xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-gnome ];
# Niri resolves `xwayland-satellite` from PATH to provide XWayland + `$DISPLAY` for X11
# clients (Steam, etc.). See https://github.com/YaLTeR/niri/issues/452
environment.systemPackages = [ pkgs.xwayland-satellite ];
})
(lib.mkIf (cfg.niri.enable && hmAvailable) {
"home-manager".sharedModules = [ self.homeManagerModules.desktopNiri ];
})
];
};
}
+218
View File
@@ -0,0 +1,218 @@
{ ... }: {
flake.nixosModules.desktopOptions =
{ config, options, lib, pkgs, self, inputs, ... }:
let
cfg = config.chiasson.desktop;
hmAvailable = lib.hasAttrByPath [ "home-manager" "sharedModules" ] options;
guiEnabled = cfg.hyprland.enable || cfg.niri.enable || cfg.plasma.enable;
dmsEnabled = cfg.shell == "dms";
sddmIni = pkgs.formats.ini { };
# Pixie SDDM theme — Qt6 main; upstream has a qt5 branch if you need it.
pixieSddm = pkgs.stdenvNoCC.mkDerivation {
pname = "pixie-sddm";
version = "0-unstable-2026-03-29";
src = pkgs.fetchFromGitHub {
owner = "xCaptaiN09";
repo = "pixie-sddm";
rev = "12a5f459ebd6d699be42c188c10976c8bb7076d7";
hash = "sha256-lmE/49ySuAZDh5xLochWqfSw9qWrIV+fYaK5T2Ckck8=";
};
dontConfigure = true;
dontBuild = true;
installPhase = ''
mkdir -p "$out/share/sddm/themes/pixie"
cp -r "$src"/. "$out/share/sddm/themes/pixie/"
'';
meta = {
description = "Pixel / Material 3 inspired SDDM theme (Qt6)";
homepage = "https://github.com/xCaptaiN09/pixie-sddm";
license = lib.licenses.mit;
platforms = lib.platforms.linux;
};
};
in
{
options.chiasson.desktop = {
defaultSession = lib.mkOption {
type = lib.types.nullOr (lib.types.enum [
"hyprland"
"niri"
"plasma"
]);
default = null;
example = "niri";
description = ''
DM session preference; `null` picks from which compositor flags are enabled. Turn on only
the compositor you use so SDDM does not drag in extras.
'';
};
shell = lib.mkOption {
type = lib.types.nullOr (lib.types.enum [ "dms" ]);
default = null;
example = "dms";
description = "Desktop shell overlay (e.g. DMS). Extend the enum when you add another.";
};
displayManager = {
variant = lib.mkOption {
type = lib.types.nullOr (lib.types.enum [
"sddm"
"dankgreeter"
]);
default = null;
description = ''
SDDM vs DankGreeter (greetd + DMS [docs](https://danklinux.com/docs/dankgreeter/nixos-flake)).
`null`: Plasma-only SDDM; Hyprland/Niri + `desktop.shell = "dms"` DankGreeter; else SDDM.
'';
};
greeter = {
configHome = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
example = "/home/olivier";
description = ''
Whose DMS files to mirror into the greeter cache. `null` first entry in `chiasson.users.enabled`.
'';
};
};
};
displayManager.sddm = {
wayland.enable = lib.mkEnableOption ''
SDDM greeter on Wayland (nicer on HiDPI; turn off if the greeter glitches on your GPU).
'' // {
default = true;
};
enableHidpi = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Passed through to `services.displayManager.sddm.enableHidpi`.";
};
theme = {
package = lib.mkOption {
type = lib.types.nullOr lib.types.package;
default = pixieSddm;
description = ''
Package providing `share/sddm/themes/<id>`. Default: bundled [Pixie](https://github.com/xCaptaiN09/pixie-sddm).
`null` Breeze. Other nixpkgs examples: `catppuccin-sddm`, `sddm-sugar-dark`.
'';
};
id = lib.mkOption {
type = lib.types.str;
default = "pixie";
description = ''
Subdir under `share/sddm/themes/` (default `pixie`). Match your theme package (e.g. catppuccin ids).
'';
};
};
settings = lib.mkOption {
type = sddmIni.type;
default = { };
description = "Extra `services.displayManager.sddm.settings` (INI).";
};
};
plasma.enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Plasma 6 session bits for this flake.";
};
defaultPackages = {
enabled = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Shared desktop utility packages (ntfs3g, cifs-utils, ).";
};
packages = lib.mkOption {
type = lib.types.listOf lib.types.package;
default = with pkgs; [
ntfs3g
cifs-utils
usbutils
xhost
];
description = "Packages merged when `defaultPackages.enabled` is true.";
};
};
extraPackages = lib.mkOption {
type = lib.types.listOf lib.types.package;
default = [ ];
description = "Extra packages on GUI hosts.";
};
homeManager = {
bundleWisdom = lib.mkOption {
type = lib.types.bool;
default = true;
description = ''
Add `wisdom` (baseline + bash) to HM `sharedModules`. Other slices still go in per-user `extraModules`.
'';
};
};
keyring = {
enable = lib.mkEnableOption ''
gnome-keyring + pam (login + sddm or greetd) + HM user service + `gcr` + `services.xserver.updateDbusEnvironment`.
niri/hyprland: `dbus-update-activation-environment` at compositor start so libsecret/Electron see `WAYLAND_DISPLAY`.
'' // {
default = true;
};
};
};
config = lib.mkMerge [
{
assertions = [
{
assertion = cfg.defaultSession != "hyprland" || cfg.hyprland.enable;
message = "chiasson.desktop.defaultSession = \"hyprland\" requires chiasson.desktop.hyprland.enable = true.";
}
{
assertion = cfg.defaultSession != "niri" || cfg.niri.enable;
message = "chiasson.desktop.defaultSession = \"niri\" requires chiasson.desktop.niri.enable = true.";
}
{
assertion = cfg.defaultSession != "plasma" || cfg.plasma.enable;
message = "chiasson.desktop.defaultSession = \"plasma\" requires chiasson.desktop.plasma.enable = true.";
}
{
assertion =
cfg.displayManager.variant != "dankgreeter" || cfg.hyprland.enable || cfg.niri.enable;
message = "chiasson.desktop.displayManager.variant = \"dankgreeter\" requires chiasson.desktop.hyprland or chiasson.desktop.niri.";
}
{
assertion = cfg.displayManager.variant != "dankgreeter" || cfg.shell == "dms";
message = "DankGreeter expects chiasson.desktop.shell = \"dms\" for DMS/matugen sync; use SDDM or enable DMS.";
}
];
}
(lib.mkIf guiEnabled {
chiasson.desktop.displayManager.variant = lib.mkDefault (
if cfg.plasma.enable && !(cfg.hyprland.enable || cfg.niri.enable) then
"sddm"
else if (cfg.hyprland.enable || cfg.niri.enable) && cfg.shell == "dms" then
"dankgreeter"
else
"sddm"
);
})
(lib.mkIf (guiEnabled && hmAvailable && cfg.homeManager.bundleWisdom) {
"home-manager".sharedModules = [ self.homeManagerModules.wisdom ];
})
(lib.mkIf (dmsEnabled && hmAvailable) {
"home-manager".sharedModules = [ self.homeManagerModules.desktopShellDms ];
})
(lib.mkIf (hmAvailable && (dmsEnabled || cfg.niri.enable)) {
"home-manager".extraSpecialArgs = { inherit inputs; };
})
];
};
}
+14
View File
@@ -0,0 +1,14 @@
{ ... }: {
flake.nixosModules.desktopPlasma =
{ config, lib, pkgs, ... }:
let
cfg = config.chiasson.desktop;
in
{
config = lib.mkIf cfg.plasma.enable {
services.desktopManager.plasma6.enable = true;
environment.etc."xdg/menus/applications.menu".text =
builtins.readFile "${pkgs.kdePackages.plasma-workspace}/etc/xdg/menus/plasma-applications.menu";
};
};
}
+90
View File
@@ -0,0 +1,90 @@
{ inputs, ... }: {
flake.nixosModules.desktopShellDmsOptions = { lib, ... }: {
options.chiasson.desktop.shells.dms = {
enableGpuTemp = lib.mkOption {
type = lib.types.bool;
default = true;
description = "GPU temp in DMS bar.";
};
obsidianSnippetsDir = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Legacy single Obsidian snippets dir for matugen.";
};
obsidianConfigDirs = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = "Vault `.obsidian/` paths for matugen.";
};
obsidianSnippetsDirs = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = "Explicit `.obsidian/snippets` paths.";
};
extraRightBarWidgets = lib.mkOption {
type = lib.types.listOf lib.types.attrs;
default = [ ];
description = "Extra right-bar widgets (prepended).";
};
enableWvkbdToggle = lib.mkEnableOption ''
wvkbd DMS plugin + bar toggle (touch / uConsole).
'';
enableRbwLockToggle = lib.mkEnableOption ''
rbw vault lock/unlock button in the DMS bar (Bitwarden CLI via rbw).
'';
rebuildCommand = lib.mkOption {
type = lib.types.nullOr (lib.types.listOf lib.types.str);
default = null;
example = [ "sudo" "nixos-rebuild" "switch" "--flake" ".#14900k" ];
description = "Command used by DMS nix-monitor widget for rebuild actions.";
};
};
};
flake.homeManagerModules.desktopShellDms = {
lib,
osConfig ? { },
...
}:
let
cfg = lib.attrByPath [ "chiasson" "desktop" "shells" "dms" ] { } osConfig;
selectedShell = lib.attrByPath [ "chiasson" "desktop" "shell" ] null osConfig;
dmsEnabled = selectedShell == "dms";
hostName = lib.attrByPath [ "networking" "hostName" ] "nixos" osConfig;
rebuildCommand =
if (cfg.rebuildCommand or null) != null then
cfg.rebuildCommand
else
[ "sudo" "nixos-rebuild" "switch" "--flake" ".#${hostName}" ];
in
{
imports = [
./home-manager/default.nix
];
config = lib.mkIf dmsEnabled {
dms.enable = true;
dms.enableGpuTemp = cfg.enableGpuTemp or true;
dms.obsidianSnippetsDir = cfg.obsidianSnippetsDir or null;
dms.obsidianConfigDirs = cfg.obsidianConfigDirs or [ ];
dms.obsidianSnippetsDirs = cfg.obsidianSnippetsDirs or [ ];
dms.enableWvkbdToggle = cfg.enableWvkbdToggle or false;
dms.enableRbwLockToggle = cfg.enableRbwLockToggle or false;
dms.extraRightBarWidgets =
(lib.optionals (cfg.enableWvkbdToggle or false) [
{
id = "wvkbdToggle";
enabled = true;
}
])
++ (lib.optionals (cfg.enableRbwLockToggle or false) [
{
id = "rbwLockToggle";
enabled = true;
}
])
++ (cfg.extraRightBarWidgets or [ ]);
programs.nix-monitor.rebuildCommand = rebuildCommand;
};
};
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,80 @@
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.Plugins
PluginComponent {
id: root
property bool vaultUnlocked: false
function refreshLockState() {
if (!statusProcess.running)
statusProcess.exec(["rbw", "unlocked"]);
}
function toggleLock() {
if (root.vaultUnlocked) {
Quickshell.execDetached(["rbw", "lock"]);
} else {
Quickshell.execDetached(["rbw", "unlock"]);
}
}
pillClickAction: () => root.toggleLock()
Component.onCompleted: refreshLockState()
Timer {
interval: 2500
repeat: true
running: true
onTriggered: root.refreshLockState()
}
Process {
id: statusProcess
command: ["rbw", "unlocked"]
onExited: (exitCode, _exitStatus) => {
root.vaultUnlocked = exitCode === 0;
}
}
horizontalBarPill: Component {
MouseArea {
implicitWidth: iconH.implicitWidth
implicitHeight: iconH.implicitHeight
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.toggleLock()
DankIcon {
id: iconH
name: root.vaultUnlocked ? "lock_open_right" : "lock"
size: root.iconSize
color: parent.containsMouse ? Theme.primary : Theme.surfaceText
}
}
}
verticalBarPill: Component {
MouseArea {
implicitWidth: iconV.implicitWidth
implicitHeight: iconV.implicitHeight
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.toggleLock()
DankIcon {
id: iconV
name: root.vaultUnlocked ? "lock_open_right" : "lock"
size: root.iconSize
color: parent.containsMouse ? Theme.primary : Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
}
@@ -0,0 +1,16 @@
import QtQuick
import qs.Common
import qs.Modules.Plugins
PluginSettings {
id: root
pluginId: "rbwLockToggle"
StyledText {
width: parent.width
text: "Shows rbw vault state with a closed lock (locked) or open lock (unlocked). Click to run `rbw unlock` (pinentry) or `rbw lock`. Requires `rbw` on PATH in the DMS session and a working pinentry."
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
}
}
@@ -0,0 +1,13 @@
{
"id": "rbwLockToggle",
"name": "Bitwarden (rbw) lock",
"description": "Bar control for rbw vault: locked/unlocked padlock; click to unlock or lock",
"version": "1.0.0",
"author": "NixOS-V2",
"type": "widget",
"capabilities": ["dankbar-widget"],
"component": "./RbwLockToggle.qml",
"settings": "./RbwLockToggleSettings.qml",
"icon": "lock_open",
"permissions": ["settings_read"]
}
@@ -0,0 +1,51 @@
import QtQuick
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.Plugins
PluginComponent {
id: root
function toggleKeyboard() {
Quickshell.execDetached(["sh", "-c", "pkill -SIGRTMIN -x wvkbd-mobintl"]);
}
pillClickAction: () => root.toggleKeyboard()
horizontalBarPill: Component {
MouseArea {
implicitWidth: icon.implicitWidth
implicitHeight: icon.implicitHeight
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.toggleKeyboard()
DankIcon {
id: icon
name: "keyboard"
size: Theme.iconSize
color: parent.containsMouse ? Theme.primary : Theme.surfaceText
}
}
}
verticalBarPill: Component {
MouseArea {
implicitWidth: iconV.implicitWidth
implicitHeight: iconV.implicitHeight
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.toggleKeyboard()
DankIcon {
id: iconV
name: "keyboard"
size: Theme.iconSize
color: parent.containsMouse ? Theme.primary : Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
}
@@ -0,0 +1,16 @@
import QtQuick
import qs.Common
import qs.Modules.Plugins
PluginSettings {
id: root
pluginId: "wvkbdToggle"
StyledText {
width: parent.width
text: "Click the keyboard icon in the bar to show or hide the on-screen keyboard (wvkbd)."
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
}
}
@@ -0,0 +1,13 @@
{
"id": "wvkbdToggle",
"name": "Virtual Keyboard Toggle",
"description": "Bar button to show/hide the wvkbd on-screen keyboard (for touch/tablet)",
"version": "1.0.0",
"author": "NixOS-V2",
"type": "widget",
"capabilities": ["dankbar-widget"],
"component": "./WvkbdToggle.qml",
"settings": "./WvkbdToggleSettings.qml",
"icon": "keyboard",
"permissions": ["settings_read"]
}
@@ -0,0 +1,119 @@
/**
* Vesktop / Vencord theme — matugen + DMS wallpaper colors.
* Derived from pywal-vencord-style mapping (NixOS-New templates/colors-discord.css).
*/
* {
/* Surfaces */
--background-primary: {{colors.background.default.hex}};
--background-secondary: {{colors.surface.default.hex}};
--background-tertiary: {{colors.surface_dim.default.hex}};
--background-primary-alt: {{colors.background.default.hex}};
--background-secondary-alt: {{colors.surface.default.hex}};
--background-tertiary-alt: {{colors.surface_dim.default.hex}};
--channeltextarea-background: {{colors.surface.default.hex}};
--custom-channel-members-bg: {{colors.surface.default.hex}};
--profile-gradient-primary-color: {{colors.surface.default.hex}};
--profile-gradient-secondary-color: {{colors.surface_variant.default.hex}};
--__header-bar-background: {{colors.surface.default.hex}} !important;
--bg-base-tertiary: {{colors.background.default.hex}};
--card-primary-bg: color-mix(in srgb, {{colors.surface.default.hex}}, black 20%);
--input-background: color-mix(in srgb, {{colors.surface.default.hex}}, black 20%);
--autocomplete-bg: color-mix(in srgb, {{colors.surface.default.hex}}, black 20%);
--background-nested-floating: color-mix(in srgb, {{colors.surface.default.hex}}, black 20%);
--background-floating: color-mix(in srgb, {{colors.surface.default.hex}}, black 20%);
--scrollbar-auto-track: color-mix(in srgb, {{colors.surface.default.hex}}, black 20%);
--scrollbar-thin-track: color-mix(in srgb, {{colors.surface.default.hex}}, black 20%);
--border-subtle: color-mix(in srgb, {{colors.surface.default.hex}}, black 20%);
--background-base-lowest: color-mix(in srgb, {{colors.surface.default.hex}}, black 20%);
--background-surface-high: color-mix(in srgb, {{colors.surface.default.hex}}, black 20%);
--button-secondary-background: color-mix(in srgb, {{colors.surface.default.hex}}, black 30%);
--background-surface-higher: color-mix(in srgb, {{colors.surface.default.hex}}, black 30%);
--background-base-lower: color-mix(in srgb, {{colors.surface.default.hex}}, black 35%);
--background-message-hover: color-mix(in srgb, {{colors.surface.default.hex}}, black 40%);
--button-secondary-background-hover: color-mix(in srgb, {{colors.surface.default.hex}}, black 40%);
--background-base-low: color-mix(in srgb, {{colors.surface.default.hex}}, black 40%);
--background-surface-highest: color-mix(in srgb, {{colors.surface.default.hex}}, black 40%);
--chat-background-default: color-mix(in srgb, {{colors.surface.default.hex}}, black 45%);
--button-secondary-background-active: color-mix(in srgb, {{colors.surface.default.hex}}, black 60%);
--primary-630: {{colors.surface_variant.default.hex}};
/* Muted / secondary chrome */
--scrollbar-auto-thumb: {{colors.on_surface_variant.default.hex}};
--scrollbar-thin-thumb: {{colors.on_surface_variant.default.hex}};
--interactive-muted: {{colors.on_surface_variant.default.hex}};
--text-muted: {{colors.on_surface_variant.default.hex}};
--background-modifier-hover: color-mix(in srgb, {{colors.on_surface_variant.default.hex}}, black 40%);
--background-modifier-active: color-mix(in srgb, {{colors.on_surface_variant.default.hex}}, black 20%);
--background-modifier-accent: {{colors.secondary_container.default.hex}};
--background-accent: {{colors.secondary.default.hex}};
--input-border: {{colors.outline.default.hex}};
--border-normal: {{colors.outline.default.hex}};
--icon-secondary: {{colors.on_surface_variant.default.hex}};
--icon-tertiary: {{colors.on_surface_variant.default.hex}};
--channel-icon: {{colors.on_surface_variant.default.hex}};
--channels-default: {{colors.on_surface_variant.default.hex}};
--header-primary: {{colors.on_surface.default.hex}};
--__lottieIconColor: {{colors.on_surface_variant.default.hex}};
--interactive-normal: {{colors.on_surface_variant.default.hex}};
/* Selection / highlights */
--red-400: {{colors.error.default.hex}};
--background-modifier-selected: {{colors.secondary_container.default.hex}};
--notice-background-positive: color-mix(in srgb, {{colors.tertiary.default.hex}}, black 75%);
--notice-text-positive: {{colors.tertiary.default.hex}};
/* Danger */
--status-danger: {{colors.error.default.hex}};
--button-outline-danger-border: {{colors.error.default.hex}};
--button-outline-danger-text: {{colors.error.default.hex}};
--button-danger-background: {{colors.error.default.hex}};
--yellow-300: {{colors.error.default.hex}};
/* Brand / accents */
--brand-experiment: {{colors.primary.default.hex}};
--brand-experiment-360: {{colors.primary.default.hex}};
--brand-experiment-500: {{colors.primary.default.hex}};
--profile-gradient-button-color: {{colors.primary.default.hex}};
--green-360: {{colors.primary.default.hex}};
/* Foreground */
--text-normal: {{colors.on_surface.default.hex}};
--interactive-active: {{colors.on_surface.default.hex}};
}
/* Status indicators (legacy Discord hex fills) */
rect[fill="#d83a41"] {
fill: {{colors.error.default.hex}} !important;
}
rect[fill="#cc954c"] {
fill: {{colors.secondary.default.hex}} !important;
}
rect[fill="#40a258"] {
fill: {{colors.primary.default.hex}} !important;
}
.wrapper_ef3116 {
background: {{colors.background.default.hex}} !important;
}
.bar_c38106 {
background: {{colors.background.default.hex}} !important;
}
+52
View File
@@ -0,0 +1,52 @@
{ inputs, ... }: {
flake.nixosModules.desktopWallpapers =
{ config, lib, pkgs, ... }:
let
cfg = config.chiasson.desktop.wallpapers;
d = config.chiasson.desktop;
guiEnabled = d.hyprland.enable || d.niri.enable || d.plasma.enable;
wallpaperDir = pkgs.stdenvNoCC.mkDerivation {
pname = "nixos-v2-wallpapers";
version = "0.1";
src = builtins.path {
path = cfg.source;
name = "wallpapers-src";
};
dontConfigure = true;
dontBuild = true;
installPhase = ''
mkdir -p "$out/share/wallpapers"
find "$src" -mindepth 1 -maxdepth 1 ! -name .git -exec cp -a {} "$out/share/wallpapers/" \;
'';
};
installPath = "${wallpaperDir}/share/wallpapers";
in
{
options.chiasson.desktop.wallpapers = {
enable = lib.mkEnableOption ''
Copy `source` into the store; sets `NIXOS_V2_WALLPAPERS` and `/etc/wallpapers`.
'' // {
default = true;
};
source = lib.mkOption {
type = lib.types.path;
default = inputs.wallpapers;
description = ''
Directory copied into the store. Default: `inputs.wallpapers`
(`git+https://git.chiasson.cloud/Olivier/wallpapers`, pinned in `flake.lock`).
'';
};
};
config = lib.mkIf (guiEnabled && cfg.enable) {
environment = {
variables.NIXOS_V2_WALLPAPERS = installPath;
sessionVariables.NIXOS_V2_WALLPAPERS = installPath;
etc."wallpapers".source = installPath;
};
};
};
}
+127
View File
@@ -0,0 +1,127 @@
{ ... }: {
flake.nixosModules.desktopWaydroid =
{ config, lib, pkgs, ... }:
# `virtualisation.waydroid.package` defaults to `waydroid-nftables` when `networking.nftables.enable`
# is true — required on recent kernels where legacy `ip_tables` / `waydroid-net.sh` (iptables) fails
# with `waydroid session start` (nixpkgs#459520).
let
cfg = config.chiasson.desktop.waydroid;
in
{
options.chiasson.desktop.waydroid = {
enable = lib.mkEnableOption ''
Waydroid + synced base props / nav mode. Needs Wayland; desktop hosts only. This module also
sets `networking.nftables.enable` (default) so NixOS uses `waydroid-nftables` needed on newer
kernels where `waydroid-net.sh` / iptables fails. For **Google Play** apps (e.g. Hot Wheels
Showcase), run `sudo waydroid init -s GAPPS -f` once after the first `nixos-rebuild` (or reset
the container if you already initialized without GAPPS); then sign in and install from the Play
Store. If `dnsmasq` reports port 53 in use, free that port (Waydroid needs it).
'';
width = lib.mkOption {
type = lib.types.int;
default = 1920;
description = "Waydroid rendering width (`persist.waydroid.width`).";
};
height = lib.mkOption {
type = lib.types.int;
default = 1080;
description = "Waydroid rendering height (`persist.waydroid.height`).";
};
multiWindows = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Enable Waydroid multi-window integration.";
};
navigationMode = lib.mkOption {
type = lib.types.enum [ "3button" "gestures" ];
default = "gestures";
description = "Maps to Waydroid `navigation_mode` secure prop (3button=0, gestures=2).";
};
extraBaseProperties = lib.mkOption {
type = lib.types.attrsOf lib.types.str;
default = { };
description = ''
Extra `key = value` pairs written into `/var/lib/waydroid/waydroid_base.prop` on activation
(same mechanism as width/height). Use for upstream tweaks e.g. NVIDIA hosts often need
`ro.hardware.gralloc=default` and `ro.hardware.egl=swiftshader`; Linux 5.18+ may need
`sys.use_memfd=true` ([NixOS Waydroid wiki](https://wiki.nixos.org/wiki/Waydroid)).
'';
};
};
config = lib.mkIf cfg.enable {
networking.nftables.enable = lib.mkDefault true;
virtualisation.waydroid.enable = true;
system.activationScripts.waydroidProps = {
text = ''
PROP_FILE="/var/lib/waydroid/waydroid_base.prop"
if [ ! -f "$PROP_FILE" ]; then
echo "waydroid: $PROP_FILE not found yet, skipping prop sync."
echo "waydroid: run 'sudo waydroid init' once, then rebuild."
exit 0
fi
set_prop() {
key="$1"
value="$2"
if ${lib.getExe pkgs.gnugrep} -q "^''${key}=" "$PROP_FILE"; then
${pkgs.gnused}/bin/sed -i "s|^''${key}=.*|''${key}=''${value}|" "$PROP_FILE"
else
printf "%s=%s\n" "$key" "$value" >> "$PROP_FILE"
fi
}
set_prop "persist.waydroid.multi_windows" "${if cfg.multiWindows then "true" else "false"}"
set_prop "persist.waydroid.width" "${toString cfg.width}"
set_prop "persist.waydroid.height" "${toString cfg.height}"
${lib.concatStringsSep "\n" (
lib.mapAttrsToList (k: v: ''
set_prop ${lib.escapeShellArg k} ${lib.escapeShellArg v}'') cfg.extraBaseProperties
)}
'';
};
systemd.services.waydroid-navigation-mode = {
description = "Set Waydroid Android navigation mode";
wantedBy = [ "multi-user.target" ];
after = [ "waydroid-container.service" ];
wants = [ "waydroid-container.service" ];
path = [ config.virtualisation.waydroid.package ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
set -eu
nav_mode="${if cfg.navigationMode == "gestures" then "2" else "0"}"
if ! systemctl -q is-active waydroid-container.service; then
exit 0
fi
for _ in $(seq 1 30); do
if waydroid shell settings put secure navigation_mode "$nav_mode" >/dev/null 2>&1; then
exit 0
fi
sleep 1
done
echo "waydroid: warning: could not set navigation_mode=$nav_mode (container not ready?)" >&2
exit 0
'';
};
};
};
}
@@ -0,0 +1,48 @@
# Export large Jellyfin media trees to nix-server. Local path must already exist
# (e.g. /mnt/test/jellyfin/{movies,tv}). On nix-server this is mounted at /mnt/nixdesk-jellyfin.
#
# After deploy: ensure Jellyfin can read files over NFS — typical fix:
# chmod -R a+rX /mnt/test/jellyfin
{ ... }:
{
# Avoid UID/GID mismatches across machines: map all NFS writes from nix-server to a single
# local system user/group on this server.
users.groups.nfsmedia = { gid = 990; };
users.users.nfsmedia = {
isSystemUser = true;
uid = 990;
group = "nfsmedia";
};
systemd.tmpfiles.settings."14900k-jellyfin-media-dirs" = {
"/mnt/test/jellyfin"."d" = { mode = "2775"; user = "nfsmedia"; group = "nfsmedia"; };
"/mnt/test/jellyfin/movies"."d" = { mode = "2775"; user = "nfsmedia"; group = "nfsmedia"; };
"/mnt/test/jellyfin/tv"."d" = { mode = "2775"; user = "nfsmedia"; group = "nfsmedia"; };
};
# Fixed ports so the firewall can allow NFS v3 helpers (see networking.firewall below).
services.nfs.server = {
enable = true;
mountdPort = 4000;
lockdPort = 4001;
statdPort = 4002;
exports = ''
/mnt/test/jellyfin 192.168.2.238(rw,sync,no_subtree_check,crossmnt,root_squash,all_squash,anonuid=990,anongid=990)
'';
};
networking.firewall.allowedTCPPorts = [
111 # portmapper
2049
4000
4001
4002
];
networking.firewall.allowedUDPPorts = [
111
2049
4000
4001
4002
];
}
+34 -1
View File
@@ -17,6 +17,7 @@
./_private/peripherals.nix
# ./_private/printing-epson.nix
./_private/displays.nix
./_private/jellyfin-nfs-export.nix
];
sops = {
@@ -37,7 +38,24 @@
group = "users";
mode = "0400";
};
services.cloudflare-warp.enable = true;
# Intel iGPU video acceleration (VA-API / QSV via oneVPL).
# This fixes common NixOS issues like `vaInitialize failed` and missing QSV encoders in apps.
hardware.graphics = {
enable = true;
extraPackages = with pkgs; [
intel-media-driver # iHD (Gen8+)
vpl-gpu-rt # oneVPL runtime (QSV)
libvdpau-va-gl
];
};
environment.sessionVariables = {
LIBVA_DRIVER_NAME = "iHD";
# Force VA-API to use the Intel iGPU render node (otherwise libva may pick NVIDIA and iHD fails).
LIBVA_DRM_DEVICE = "/dev/dri/renderD128";
};
chiasson.system.caching.attic = {
enable = true;
@@ -86,7 +104,20 @@
palera1n.enable = true;
uconsoleKernelBuilder.enable = true;
extraPackages = [ pkgs.sops pkgs.nodejs_22 ];
extraPackages = with pkgs; [
sops
nodejs_22
ffmpeg
bento4
yt-dlp
# Native install (avoid flatpak sandbox issues for QSV/VAAPI).
handbrake
# Diagnostics
libva-utils # vainfo
];
networking = {
hostName = "nixdesk";
@@ -102,6 +133,7 @@
self.homeManagerModules.wisdomTerminalsKitty
self.homeManagerModules.wisdomBrowsersEdge
self.homeManagerModules.wisdomBrowsersFlow
self.homeManagerModules.wisdomBrowsersOrion
self.homeManagerModules.wisdomEditorsCursor
self.homeManagerModules.wisdomEditorsObsidian
self.homeManagerModules.wisdomShellYazi
@@ -135,6 +167,7 @@
browsers.edge.enable = true;
browsers.flow.enable = false;
browsers.orion.enable = true;
editors.cursor.enable = true;
editors.obsidian.enable = true;
+213
View File
@@ -0,0 +1,213 @@
{ ... }: {
flake.nixosModules.systemServiceImmich =
{ config, lib, pkgs, ... }:
let
cfg = config.chiasson.system.services.immich;
in
{
options.chiasson.system.services.immich = with lib; {
enable = mkEnableOption "Immich stack (server + machine-learning + redis + postgres).";
version = mkOption {
type = types.str;
default = "release";
description = "Immich image tag to deploy.";
};
host = mkOption {
type = types.str;
default = "0.0.0.0";
description = "Host interface to bind Immich server.";
};
port = mkOption {
type = types.int;
default = 2283;
description = "Host TCP port mapped to Immich server port 2283.";
};
openFirewall = mkOption {
type = types.bool;
default = true;
description = "Open firewall for Immich server port.";
};
timezone = mkOption {
type = types.str;
default = "UTC";
description = "Timezone passed to Immich services via TZ.";
};
uploadLocation = mkOption {
type = types.str;
default = "/var/lib/immich/library";
description = "Host path used for Immich uploads/library.";
};
environmentFiles = mkOption {
type = types.listOf types.path;
default = [ ];
description = ''
Docker `--env-file` paths for **immich-database** and **immich-server** (after inline `-e`).
Use a sops template with `POSTGRES_PASSWORD` and `DB_PASSWORD` (same value) when using
`sops.placeholder` for the DB secret; then set `postgres.password` to empty.
'';
};
postgres = {
image = mkOption {
type = types.str;
default = "ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:bcf63357191b76a916ae5eb93464d65c07511da41e3bf7a8416db519b40b1c23";
description = "Immich recommended Postgres image with vector extensions.";
};
user = mkOption {
type = types.str;
default = "postgres";
description = "Immich Postgres username.";
};
password = mkOption {
type = types.str;
default = "";
description = ''
Immich Postgres password (inline `POSTGRES_PASSWORD` / `DB_PASSWORD`).
Leave empty when using `environmentFiles` with those keys from a sops template.
'';
};
database = mkOption {
type = types.str;
default = "immich";
description = "Immich Postgres database name.";
};
};
};
config = lib.mkIf cfg.enable {
assertions = [
{
assertion =
cfg.postgres.password != "" || cfg.environmentFiles != [ ];
message =
"chiasson.system.services.immich: set postgres.password or environmentFiles (e.g. sops-rendered POSTGRES_PASSWORD + DB_PASSWORD).";
}
];
virtualisation = {
docker.enable = true;
oci-containers = {
backend = "docker";
containers = {
immich-redis = {
image = "docker.io/valkey/valkey:9@sha256:3b55fbaa0cd93cf0d9d961f405e4dfcc70efe325e2d84da207a0a8e6d8fde4f9";
extraOptions = [ "--network=immich-network" ];
};
immich-database = {
image = cfg.postgres.image;
environment =
{
POSTGRES_USER = cfg.postgres.user;
POSTGRES_DB = cfg.postgres.database;
POSTGRES_INITDB_ARGS = "--data-checksums";
}
// lib.optionalAttrs (cfg.postgres.password != "") {
POSTGRES_PASSWORD = cfg.postgres.password;
};
environmentFiles = cfg.environmentFiles;
volumes = [ "immich-postgres:/var/lib/postgresql/data" ];
extraOptions = [
"--network=immich-network"
"--shm-size=128mb"
];
};
immich-machine-learning = {
image = "ghcr.io/immich-app/immich-machine-learning:${cfg.version}";
environment = {
TZ = cfg.timezone;
};
volumes = [ "immich-model-cache:/cache" ];
extraOptions = [ "--network=immich-network" ];
};
immich-server = {
image = "ghcr.io/immich-app/immich-server:${cfg.version}";
dependsOn = [
"immich-redis"
"immich-database"
"immich-machine-learning"
];
ports = [ "${cfg.host}:${toString cfg.port}:2283" ];
environment =
{
TZ = cfg.timezone;
DB_HOSTNAME = "immich-database";
DB_USERNAME = cfg.postgres.user;
DB_DATABASE_NAME = cfg.postgres.database;
REDIS_HOSTNAME = "immich-redis";
IMMICH_MACHINE_LEARNING_URL = "http://immich-machine-learning:3003";
UPLOAD_LOCATION = "/data";
}
// lib.optionalAttrs (cfg.postgres.password != "") { DB_PASSWORD = cfg.postgres.password; };
environmentFiles = cfg.environmentFiles;
volumes = [
"${cfg.uploadLocation}:/data"
"/etc/localtime:/etc/localtime:ro"
];
extraOptions = [
"--network=immich-network"
"--pull=always"
];
};
};
};
};
systemd.services.immich-network = {
description = "Create Docker network for Immich";
after = [ "docker.service" ];
requires = [ "docker.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig.Type = "oneshot";
script = ''
${pkgs.docker}/bin/docker network inspect immich-network >/dev/null 2>&1 || \
${pkgs.docker}/bin/docker network create immich-network
'';
};
systemd.services."docker-immich-redis" = {
after = [ "immich-network.service" ];
requires = [ "immich-network.service" ];
};
systemd.services."docker-immich-database" = {
after = [ "immich-network.service" ];
requires = [ "immich-network.service" ];
};
systemd.services."docker-immich-machine-learning" = {
after = [ "immich-network.service" ];
requires = [ "immich-network.service" ];
};
systemd.services."docker-immich-server" = {
after = [
"immich-network.service"
"docker-immich-redis.service"
"docker-immich-database.service"
"docker-immich-machine-learning.service"
];
requires = [
"immich-network.service"
"docker-immich-redis.service"
"docker-immich-database.service"
"docker-immich-machine-learning.service"
];
};
networking.firewall.allowedTCPPorts = lib.optionals cfg.openFirewall [ cfg.port ];
};
};
}
+19
View File
@@ -0,0 +1,19 @@
{ ... }: {
flake.homeManagerModules.wisdomBrowsersChrome =
{ config, lib, pkgs, ... }:
let
root = config.chiasson.home;
cfg = config.chiasson.home.browsers.chrome;
in
{
options.chiasson.home.browsers.chrome.enable = lib.mkEnableOption ''
Chrome (unfree, needs `allowUnfree`); skipped if nixpkgs has no build for this platform.
'';
config = lib.mkIf (root.enable && cfg.enable) {
home.packages = lib.optional (
lib.meta.availableOn pkgs.stdenv.hostPlatform pkgs.google-chrome
) pkgs.google-chrome;
};
};
}
+17
View File
@@ -0,0 +1,17 @@
{ ... }: {
flake.homeManagerModules.wisdomBrowsersEdge =
{ config, lib, pkgs, ... }:
let
root = config.chiasson.home;
cfg = config.chiasson.home.browsers.edge;
in
{
options.chiasson.home.browsers.edge.enable = lib.mkEnableOption "Edge (unfree); skipped if unavailable on this platform.";
config = lib.mkIf (root.enable && cfg.enable) {
home.packages = lib.optional (
lib.meta.availableOn pkgs.stdenv.hostPlatform pkgs.microsoft-edge
) pkgs.microsoft-edge;
};
};
}
+67
View File
@@ -0,0 +1,67 @@
{ ... }: {
flake.homeManagerModules.wisdomBrowsersFlow =
{ config, lib, pkgs, ... }:
let
root = config.chiasson.home;
cfg = config.chiasson.home.browsers.flow;
flow-browser =
let
pname = "flow-browser";
version = "0.11.0";
suffix = if pkgs.stdenv.hostPlatform.isAarch64 then "arm64" else "x86_64";
hash =
if pkgs.stdenv.hostPlatform.isAarch64 then
"sha256-rTRKbNyVRJAw7ZyDR6kx+XJ4rWmErZqA0b6LP9t5eOA="
else
"sha256-/Tca4uUBfgbZQEeXdYkCz6CWxqvCl40CQpACFry1k9s=";
src = pkgs.fetchurl {
url = "https://github.com/MultiboxLabs/flow-browser/releases/download/v${version}/flow-browser-${version}-${suffix}.AppImage";
inherit hash;
};
appimageContents = pkgs.appimageTools.extractType2 { inherit pname version src; };
in
pkgs.appimageTools.wrapType2 {
inherit pname version src;
nativeBuildInputs = [ pkgs.makeWrapper ];
extraInstallCommands = ''
wrapProgram $out/bin/${pname} \
--add-flags "\''${NIXOS_OZONE_WL:+\''${WAYLAND_DISPLAY:+--ozone-platform-hint=auto --enable-features=WaylandWindowDecorations --enable-wayland-ime=true}}"
install -m 444 -D ${appimageContents}/flow-browser.desktop -t $out/share/applications
substituteInPlace $out/share/applications/flow-browser.desktop \
--replace-fail 'Exec=AppRun --ozone-platform-hint=auto' 'Exec=${pname} --ozone-platform-hint=auto'
install -m 444 -D ${appimageContents}/usr/share/icons/hicolor/512x512/apps/flow-browser.png \
$out/share/icons/hicolor/512x512/apps/flow-browser.png
'';
meta = {
description = "Chromium-based browser (upstream AppImage)";
homepage = "https://github.com/MultiboxLabs/flow-browser";
license = lib.licenses.gpl3Plus;
sourceProvenance = with lib.sourceTypes; [ binaryNativeCode ];
platforms = [ "x86_64-linux" "aarch64-linux" ];
mainProgram = pname;
maintainers = [ ];
};
};
in
{
options.chiasson.home.browsers.flow.enable = lib.mkEnableOption ''
[Flow](https://github.com/MultiboxLabs/flow-browser) upstream AppImage wrapped for NixOS.
'';
config = lib.mkIf (root.enable && cfg.enable) {
home.packages = lib.optional (
lib.meta.availableOn pkgs.stdenv.hostPlatform flow-browser
) flow-browser;
};
};
}
+59
View File
@@ -0,0 +1,59 @@
{ ... }: {
flake.homeManagerModules.wisdomBrowsersOrion =
{ config, lib, pkgs, ... }:
let
root = config.chiasson.home;
cfg = config.chiasson.home.browsers.orion;
pname = "oriongtk";
version = "0.3.0";
flatpakBundle = pkgs.fetchurl {
url = "https://cdn.kagi.com/downloads/oriongtk.${version}.flatpak";
hash = "sha256-0NOWPS2Yv5NpnTxqsiMvshHFyTyDotPi964/2og/bCw=";
};
appId = "com.kagi.OrionGtk";
oriongtk = pkgs.runCommand "oriongtk-${version}"
{
nativeBuildInputs = [ pkgs.makeWrapper ];
passthru = {
inherit pname version;
};
}
''
mkdir -p "$out/bin"
makeWrapper ${pkgs.flatpak}/bin/flatpak "$out/bin/${pname}" \
--add-flags "run" \
--add-flags ${lib.escapeShellArg appId}
'';
in
{
options.chiasson.home.browsers.orion.enable = lib.mkEnableOption ''
[Orion](https://orionbrowser.com/) (Kagi) installs the upstream Flatpak bundle and provides `oriongtk`.
'';
config = lib.mkIf (root.enable && cfg.enable) {
home.packages = [ oriongtk ];
home.activation.oriongtkFlatpak = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
set -eu
if [ ! -x "${pkgs.flatpak}/bin/flatpak" ]; then
echo "oriongtk: flatpak missing; enable Flatpak (e.g. services.flatpak on NixOS)." >&2
exit 1
fi
echo "oriongtk: ensuring ${appId} from ${flatpakBundle} (user)"
# `--or-update` still exits non-zero when the same ref is already installed from this bundle;
# `--reinstall` is idempotent for HM switches (uninstall first only if present).
${pkgs.flatpak}/bin/flatpak --user install \
--assumeyes \
--noninteractive \
--reinstall \
--bundle \
${lib.escapeShellArg (builtins.toString flatpakBundle)}
'';
};
};
}
+85
View File
@@ -0,0 +1,85 @@
{ inputs, ... }: {
flake.homeManagerModules.wisdomBrowsersZen =
{ config, lib, pkgs, ... }:
let
root = config.chiasson.home;
cfg = config.chiasson.home.browsers.zen;
in
{
imports = [ inputs.zen-browser.homeModules.beta ];
options.chiasson.home.browsers.zen.enable = lib.mkEnableOption "Zen Browser + locked-down policies / extensions.";
config = lib.mkIf (root.enable && cfg.enable) {
programs.zen-browser = {
enable = true;
policies = {
PasswordManagerEnabled = false;
AutofillCreditCardEnabled = false;
AutofillAddressEnabled = false;
DisableAppUpdate = true;
DisableFeedbackCommands = true;
DisableFirefoxStudies = true;
DisablePocket = true;
DisableTelemetry = true;
OfferToSaveLogins = false;
EnableTrackingProtection = {
Value = true;
Locked = true;
Cryptomining = true;
Fingerprinting = true;
};
ExtensionSettings = {
"{446900e4-71c2-419f-a6a7-df9c091e268b}" = {
install_url = "https://addons.mozilla.org/firefox/downloads/latest/bitwarden-password-manager/latest.xpi";
installation_mode = "normal_installed";
};
"uBlock0@raymondhill.net" = {
install_url = "https://addons.mozilla.org/firefox/downloads/latest/ublock-origin/latest.xpi";
installation_mode = "normal_installed";
};
"{762f9885-5a13-4abd-9c77-433dcd38b8fd}" = {
install_url = "https://addons.mozilla.org/firefox/downloads/latest/return-youtube-dislikes/latest.xpi";
installation_mode = "normal_installed";
};
};
};
};
home.packages = [
(pkgs.writeShellApplication {
name = "extract-firefox-extension";
runtimeInputs = with pkgs; [
wget
unzip
jq
];
text = ''
if [ -z "$1" ]; then
echo "usage: $0 <firefox-addon-url>"
exit 1
fi
PLUGIN_URL="$1"
TEMP_DIR="extension-id-$(date +%s)"
mkdir "$TEMP_DIR" || exit 1
cd "$TEMP_DIR" || exit 1
DOWNLOAD_URL=$(echo "$PLUGIN_URL" \
| sed -E 's|https://addons.mozilla.org/firefox/downloads/file/[0-9]+/([^/]+)-[^/]+\.xpi|\1|' \
| tr '_' '-' \
| awk '{print "https://addons.mozilla.org/firefox/downloads/latest/" $1 "/latest.xpi"}')
wget -q "$DOWNLOAD_URL" -O latest.xpi || { cd ..; rm -rf "$TEMP_DIR"; exit 1; }
unzip -q latest.xpi -d unpacked || { cd ..; rm -rf "$TEMP_DIR"; exit 1; }
jq -r '.browser_specific_settings.gecko.id' unpacked/manifest.json || { cd ..; rm -rf "$TEMP_DIR"; exit 1; }
cd ..
rm -rf "$TEMP_DIR"
'';
})
];
};
};
}
+128
View File
@@ -0,0 +1,128 @@
{ ... }: {
flake.homeManagerModules.wisdomDesktopGtkQtTheming =
{ config, lib, pkgs, ... }:
let
root = config.chiasson.home;
cfg = config.chiasson.home.desktop.theming;
in
{
options.chiasson.home.desktop.theming = {
enable = lib.mkEnableOption ''
WhiteSur GTK + icon themes, Phinger cursor, and Qt via the KDE platform theme same idea as
the old `home-shared.nix` stack for Hyprland/Niri (no full Plasma session required). Optional
`dank-colors.css` import for DMS/matugen GTK accents.
'';
matugenGtkColors = lib.mkOption {
type = lib.types.bool;
default = true;
description = ''
Add `@import url("dank-colors.css");` to gtk3/gtk4 extraCss. DMS/matugen writes
`~/.config/gtk-3.0` / `gtk-4.0` `dank-colors.css` when dynamic theming is on disable if
you use this module without DMS.
'';
};
gtkTheme = {
name = lib.mkOption {
type = lib.types.str;
default = "WhiteSur-Dark";
};
package = lib.mkOption {
type = lib.types.package;
default = pkgs.whitesur-gtk-theme;
};
};
iconTheme = {
name = lib.mkOption {
type = lib.types.str;
default = "WhiteSur-dark";
};
package = lib.mkOption {
type = lib.types.package;
default = pkgs.whitesur-icon-theme;
};
};
cursor = {
name = lib.mkOption {
type = lib.types.str;
default = "phinger-cursors-dark";
};
package = lib.mkOption {
type = lib.types.package;
default = pkgs.phinger-cursors;
};
size = lib.mkOption {
type = lib.types.int;
default = 32;
};
};
qt = {
platformTheme = lib.mkOption {
type = lib.types.str;
default = "kde";
description = ''
`QT_QPA_PLATFORMTHEME` (e.g. `kde` for Qt/KDE integration, matches previous flake).
'';
};
extraPackages = lib.mkOption {
type = lib.types.listOf lib.types.package;
default = [ pkgs.whitesur-kde pkgs.qt6Packages.qt6ct ];
description = "Extra packages (KDE look-and-feel + qt6ct GUI).";
};
};
};
config = lib.mkIf (root.enable && cfg.enable) (lib.mkMerge [
{
home.packages = cfg.qt.extraPackages;
home.sessionVariables = {
QT_QPA_PLATFORMTHEME = cfg.qt.platformTheme;
};
home.pointerCursor = {
name = cfg.cursor.name;
package = cfg.cursor.package;
size = cfg.cursor.size;
gtk.enable = true;
x11.enable = true;
};
gtk = {
enable = true;
theme = {
name = cfg.gtkTheme.name;
package = cfg.gtkTheme.package;
};
iconTheme = {
name = cfg.iconTheme.name;
package = cfg.iconTheme.package;
};
cursorTheme = {
name = cfg.cursor.name;
package = cfg.cursor.package;
size = cfg.cursor.size;
};
gtk3.extraConfig = {
gtk-application-prefer-dark-theme = 1;
};
gtk4.extraConfig = {
gtk-application-prefer-dark-theme = 1;
};
};
}
(lib.mkIf cfg.matugenGtkColors {
gtk.gtk3.extraCss = ''
@import url("dank-colors.css");
'';
gtk.gtk4.extraCss = ''
@import url("dank-colors.css");
'';
})
]);
};
}
+219
View File
@@ -0,0 +1,219 @@
{ ... }: {
flake.homeManagerModules.wisdomDesktopScreenshot =
{ config, lib, pkgs, ... }:
let
root = config.chiasson.home;
cfg = config.chiasson.home.desktop.screenshot;
hyprlandHm = lib.attrByPath [ "wayland" "windowManager" "hyprland" ] { } config;
hyprlandHmEnabled = hyprlandHm.enable or false;
keyOk = cfg.swiftshareApiKeyFile != null && cfg.swiftshareApiKeyFile != "";
in
{
options.chiasson.home.desktop.screenshot.enable = lib.mkEnableOption ''
grim/slurp/swappy + SwiftShare helpers; Hyprland binds if HM Hyprland is on.
'';
options.chiasson.home.desktop.screenshot.swiftshareApiKeyFile = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = ''
File with SwiftShare API key (sops path is fine). Required when screenshot module is on.
'';
};
config = lib.mkMerge [
(lib.mkIf (root.enable && cfg.enable) {
assertions = [
{
assertion = keyOk;
message = "chiasson.home.desktop.screenshot: set chiasson.home.desktop.screenshot.swiftshareApiKeyFile to your SwiftShare API key file path.";
}
];
})
(lib.mkIf (root.enable && cfg.enable && keyOk) (
let
apiKeyFile = cfg.swiftshareApiKeyFile;
in
lib.mkMerge [
{
home.packages = with pkgs; [
grim
slurp
swappy
wl-clipboard
libnotify
(writeShellScriptBin "swiftshare-upload" ''
#!${pkgs.bash}/bin/bash
set -euo pipefail
COPY_URL=0
if [ "$#" -ge 1 ] && [ "$1" = "--copy-url" ]; then
COPY_URL=1
shift
fi
APP_NAME=""
if [ "$#" -ge 2 ] && [ "$1" = "--app-name" ]; then
APP_NAME="$2"
shift 2
fi
API_KEY_FILE=${lib.escapeShellArg apiKeyFile}
if [ -r "$API_KEY_FILE" ]; then
SWIFTSHARE_API_KEY="$(tr -d '\n' < "$API_KEY_FILE")"
fi
if [ -z "''${SWIFTSHARE_API_KEY:-}" ]; then
${pkgs.libnotify}/bin/notify-send "SwiftShare upload" "SwiftShare API key missing (expected readable: $API_KEY_FILE)"
echo "Error: SwiftShare API key missing (expected readable: $API_KEY_FILE)" >&2
exit 1
fi
IMAGE_FILE=""
RESPONSE_FILE=""
cleanup() {
if [ -n "$RESPONSE_FILE" ] && [ -f "$RESPONSE_FILE" ]; then
rm -f "$RESPONSE_FILE"
fi
}
trap cleanup EXIT
if [ "$#" -ge 1 ] && [ "$1" != "-" ]; then
IMAGE_FILE="$1"
if [ "''${IMAGE_FILE#'/'}" = "''${IMAGE_FILE}" ]; then
IMAGE_FILE="$(${pkgs.coreutils}/bin/readlink -f "''${IMAGE_FILE}")"
fi
if [ ! -f "$IMAGE_FILE" ]; then
echo "Error: file not found: $IMAGE_FILE" >&2
exit 1
fi
else
APP_NAME="''${APP_NAME:-screenshot}"
APP_NAME="''${APP_NAME%% *}"
APP_NAME="''${APP_NAME//[^A-Za-z0-9]/}"
APP_NAME="''${APP_NAME,,}"
if [ -z "$APP_NAME" ]; then
APP_NAME="screenshot"
fi
IMAGE_FILE="$(${pkgs.coreutils}/bin/mktemp --suffix=.png "''${TMPDIR:-/tmp}/''${APP_NAME}_XXXXXX")"
cat > "$IMAGE_FILE"
fi
if [ ! -s "$IMAGE_FILE" ]; then
${pkgs.libnotify}/bin/notify-send "SwiftShare" "Empty capture (maybe canceled) not uploading"
echo "Empty image file, not uploading." >&2
exit 0
fi
RESPONSE_FILE="$(mktemp)"
set +e
HTTP_STATUS="$(${pkgs.curl}/bin/curl -sS -o "''${RESPONSE_FILE}" -w '%{http_code}' \
-X POST "https://swiftshare.cloud/api/upload/sharex" \
-F "upload=@''${IMAGE_FILE}" \
-F "apiKey=''${SWIFTSHARE_API_KEY}")"
CURL_EXIT=$?
set -e
RESPONSE="$(cat "''${RESPONSE_FILE}")"
if [ "''${CURL_EXIT}" -ne 0 ]; then
${pkgs.libnotify}/bin/notify-send "SwiftShare upload failed" "Network or HTTP error (curl exit ''${CURL_EXIT})"
echo "SwiftShare upload failed (curl exit ''${CURL_EXIT})." >&2
echo "Response body:" >&2
echo "''${RESPONSE}" >&2
exit 1
fi
if ! echo "''${HTTP_STATUS}" | grep -qE '^2[0-9][0-9]$'; then
ERROR_MSG="$(${pkgs.jq}/bin/jq -r '.error // empty' <<< "''${RESPONSE}")"
if [ -z "''${ERROR_MSG}" ] || [ "''${ERROR_MSG}" = "null" ]; then
ERROR_MSG="Failed to upload file"
fi
${pkgs.libnotify}/bin/notify-send "SwiftShare upload failed (''${HTTP_STATUS})" "''${ERROR_MSG}"
echo "SwiftShare upload failed (HTTP ''${HTTP_STATUS}): ''${ERROR_MSG}" >&2
exit 1
fi
URL="$(${pkgs.jq}/bin/jq -r '.url // empty' <<< "''${RESPONSE}")"
THUMBNAIL="$(${pkgs.jq}/bin/jq -r '.thumbnail // empty' <<< "''${RESPONSE}")"
if [ -z "$URL" ] || [ "$URL" = "null" ]; then
${pkgs.libnotify}/bin/notify-send "SwiftShare upload failed" "Could not parse URL from response"
echo "Upload failed. Raw response:" >&2
echo "''${RESPONSE}" >&2
exit 1
fi
echo "$URL"
if [ -n "$THUMBNAIL" ] && [ "$THUMBNAIL" != "null" ]; then
echo "$THUMBNAIL"
fi
if [ "$COPY_URL" = "1" ]; then
${pkgs.wl-clipboard}/bin/wl-copy <<< "$URL"
fi
if [ -n "$IMAGE_FILE" ] && [ -f "$IMAGE_FILE" ]; then
${pkgs.libnotify}/bin/notify-send \
-a "SwiftShare" \
-i "$IMAGE_FILE" \
-h string:image-path:"$IMAGE_FILE" \
"SwiftShare upload" "Uploaded image: $URL"
else
${pkgs.libnotify}/bin/notify-send "SwiftShare upload" "Uploaded image: $URL"
fi
'')
(writeShellScriptBin "swiftshare-screenshot" ''
#!${pkgs.bash}/bin/bash
set -euo pipefail
LOG_DIR="$HOME/.local/state/swiftshare"
mkdir -p "$LOG_DIR"
LOG_FILE="$LOG_DIR/screenshot.log"
APP_CLASS="$(${pkgs.hyprland}/bin/hyprctl activewindow -j 2>/dev/null | ${pkgs.jq}/bin/jq -r '.class // .initialClass // empty' 2>/dev/null || true)"
APP_CLASS="''${APP_CLASS%% *}"
APP_CLASS="''${APP_CLASS//[^A-Za-z0-9]/}"
APP_CLASS="''${APP_CLASS,,}"
if [ -z "$APP_CLASS" ]; then
APP_CLASS="screenshot"
fi
GEOM="$(${pkgs.slurp}/bin/slurp)"
SLURP_EXIT=$?
if [ "$SLURP_EXIT" -ne 0 ] || [ -z "$GEOM" ]; then
${pkgs.libnotify}/bin/notify-send "SwiftShare" "Capture canceled"
{
echo "==== $(date) ==== capture canceled (slurp exit $SLURP_EXIT, geom='$GEOM')"
} >>"$LOG_FILE" 2>&1
exit 0
fi
{
echo "==== $(date) ===="
echo "Geometry: $GEOM"
${pkgs.grim}/bin/grim -g "$GEOM" - | ${pkgs.swappy}/bin/swappy -f - -o - | swiftshare-upload --copy-url --app-name "$APP_CLASS"
} >>"$LOG_FILE" 2>&1
'')
];
}
(lib.mkIf hyprlandHmEnabled {
wayland.windowManager.hyprland.settings = {
bind = [
", Print, exec, grim -g \"$(slurp)\" - | wl-copy"
"Control, Print, exec, grim -g \"$(slurp)\" - | swappy -f -"
"SUPER, Print, exec, swiftshare-screenshot"
];
windowrule = [
"float on, opacity 1.0 override, match:class ^(swappy)$"
];
};
})
]
))
];
};
}
+60
View File
@@ -0,0 +1,60 @@
{ inputs, ... }: {
flake.homeManagerModules.wisdomEditorsCursor =
{ config, lib, pkgs, ... }:
let
root = config.chiasson.home;
cfg = config.chiasson.home.editors.cursor;
cursorPkgs = inputs.cursor.packages.${pkgs.stdenv.hostPlatform.system} or { };
cursorPkg =
if cursorPkgs ? cursor then
cursorPkgs.cursor
else if cursorPkgs ? default then
cursorPkgs.default
else
null;
# NixOS-New `home-shared.nix`: `cursor-cli` alongside the AppImage. nixpkgs now names this
# `cursor-agent`; keep both for compatibility across pins.
defaultAgentPkg =
if pkgs ? cursor-agent then
pkgs.cursor-agent
else if pkgs ? cursor-cli then
pkgs.cursor-cli
else
null;
in
{
options.chiasson.home.editors.cursor = {
enable = lib.mkEnableOption "Cursor editor from the `cursor` flake input.";
setAsDefaultEditor = lib.mkOption {
type = lib.types.bool;
default = true;
description = "`EDITOR` / `VISUAL` `cursor --wait`.";
};
agent = {
enable = lib.mkEnableOption ''
Cursor Agent CLI (`cursor-agent` in current nixpkgs; older pins used `cursor-cli`).
'' // {
default = true;
};
package = lib.mkOption {
type = with lib.types; nullOr package;
default = defaultAgentPkg;
defaultText = "pkgs.cursor-agent or pkgs.cursor-cli or null";
description = ''
Package providing the `cursor-agent` CLI. Set to `null` to omit the CLI while keeping the GUI app.
'';
};
};
};
config = lib.mkIf (root.enable && cfg.enable && cursorPkg != null) {
home.packages =
[ cursorPkg ]
++ lib.optionals (cfg.agent.enable && cfg.agent.package != null) [ cfg.agent.package ];
home.sessionVariables = lib.mkIf cfg.setAsDefaultEditor {
EDITOR = "cursor --wait";
VISUAL = "cursor --wait";
};
};
};
}
+76
View File
@@ -0,0 +1,76 @@
{ ... }: {
flake.homeManagerModules.wisdomFilebrowsersDolphin =
{ config, lib, pkgs, ... }:
let
root = config.chiasson.home;
cfg = root.filebrowsers.dolphin;
in
{
options.chiasson.home.filebrowsers.dolphin.enable = lib.mkEnableOption "Dolphin + declarative dolphinrc.";
config = lib.mkIf (root.enable && cfg.enable) {
home.packages = [ pkgs.kdePackages.dolphin ];
xdg.configFile."dolphinrc".text = ''
[ContentDisplay]
UsePermissionsFormat=CombinedFormat
[General]
StartupPath=~
DoubleClickViewAction=show_hidden_files
ShowFullPath=true
ShowFullPathInTitlebar=true
ShowStatusBar=FullWidth
UseTabForSwitchingSplitView=true
Version=202
ViewPropsTimestamp=2025,11,17,23,21,57.762
[KFileDialog Settings]
Places Icons Auto-resize=false
Places Icons Static Size=22
[MainWindow]
MenuBar=Disabled
ToolBarsMovable=Disabled
[PreviewSettings]
Plugins=appimagethumbnail,audiothumbnail,blenderthumbnail,comicbookthumbnail,cursorthumbnail,djvuthumbnail,ebookthumbnail,exrthumbnail,directorythumbnail,fontthumbnail,imagethumbnail,jpegthumbnail,kraorathumbnail,windowsexethumbnail,windowsimagethumbnail,mobithumbnail,opendocumentthumbnail,gsthumbnail,rawthumbnail,svgthumbnail,ffmpegthumbs
[IconsMode]
IconSize=48
PreviewSize=48
TextLines=2
UseThumbnails=true
[DetailsMode]
FontWeight=50
HighlightEntireRow=true
[ViewProperties]
Mode=1
ColumnWidths=50,50,50,50,50,50,50,50,50,50
SortColumn=0
SortOrder=0
SortFoldersFirst=true
SortHiddenLast=false
SortCaseSensitively=false
ShowPreviews=true
ShowInGroups=false
ShowFoldersFirst=true
ShowHiddenFilesLast=false
NaturalSorting=true
[ContextMenu]
ShowCopyToMenu=true
ShowMoveToMenu=true
[Search]
Location=Everywhere
[SettingsWindow]
SidebarWidth=180
SplitterState=AAAA/wAAAAD9AAAAAAAAAAAAAAABAAAAAQAAAAEAAAAAQAAAAEAAAAA=
'';
};
};
}
@@ -0,0 +1,61 @@
{ ... }: {
flake.homeManagerModules.wisdomHardwareUconsoleGamepad =
{ config, lib, pkgs, ... }:
let
root = config.chiasson.home;
cfg = config.chiasson.home.hardware.uconsoleGamepad;
in
{
options.chiasson.home.hardware.uconsoleGamepad.enable = lib.mkEnableOption ''
uConsole gamepad antimicrox profile + Hyprland exec-once when HM Hyprland is on.
'';
config = lib.mkIf (root.enable && cfg.enable) (lib.mkMerge [
{
home.packages = [ pkgs.antimicrox ];
home.file.".config/antimicrox/uconsole.gamecontroller.amgp".text = ''
<?xml version="1.0" encoding="UTF-8"?>
<gamecontroller configversion="19" appversion="3.5.1">
<sdlname>Clockwork Pi DevTerm</sdlname>
<uniqueID>030000fdaf1e00002400000010010000785536</uniqueID>
<stickAxisAssociation index="2" xAxis="3" yAxis="4"/>
<stickAxisAssociation index="1" xAxis="1" yAxis="2"/>
<vdpadButtonAssociations index="1">
<vdpadButtonAssociation axis="0" button="12" direction="1"/>
<vdpadButtonAssociation axis="0" button="13" direction="4"/>
<vdpadButtonAssociation axis="0" button="14" direction="8"/>
<vdpadButtonAssociation axis="0" button="15" direction="2"/>
</vdpadButtonAssociations>
<names>
<controlstickname index="2">Stick 2</controlstickname>
<controlstickname index="1">Stick 1</controlstickname>
</names>
<sets>
<set index="1">
<trigger index="6">
<throttle>positivehalf</throttle>
</trigger>
<trigger index="5">
<throttle>positivehalf</throttle>
</trigger>
<button index="2">
<slots>
<slot>
<code>0x1000022</code>
<mode>keyboard</mode>
</slot>
</slots>
</button>
</set>
</sets>
</gamecontroller>
'';
}
(lib.mkIf (config.wayland.windowManager.hyprland.enable or false) {
wayland.windowManager.hyprland.settings.exec-once = lib.mkAfter [
"antimicrox --hidden --no-tray --profile ${config.home.homeDirectory}/.config/antimicrox/uconsole.gamecontroller.amgp &"
];
})
]);
};
}