Rebase to flake parts #8
This commit is contained in:
@@ -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
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -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 ];
|
||||
})
|
||||
];
|
||||
})
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -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 { })
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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 ];
|
||||
})
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -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; };
|
||||
})
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -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";
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
];
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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 ];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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)}
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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"
|
||||
'';
|
||||
})
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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");
|
||||
'';
|
||||
})
|
||||
]);
|
||||
};
|
||||
}
|
||||
@@ -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)$"
|
||||
];
|
||||
};
|
||||
})
|
||||
]
|
||||
))
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -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";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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 &"
|
||||
];
|
||||
})
|
||||
]);
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user