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
'';
};
};
};
}