mirror of
https://github.com/nix-community/home-manager.git
synced 2026-06-05 21:02:51 +00:00
`literalExpression` is intended just to signify code that needs to stay a string that gets represented exactly as-is for docs. It has been misused heavily and people get confused repeatedly on when or not to use it because of the rampant misuse.
376 lines
10 KiB
Nix
376 lines
10 KiB
Nix
{
|
|
config,
|
|
lib,
|
|
pkgs,
|
|
...
|
|
}:
|
|
|
|
let
|
|
inherit (lib)
|
|
mkOption
|
|
mkEnableOption
|
|
types
|
|
literalExpression
|
|
;
|
|
|
|
cfg = config.services.twmn;
|
|
|
|
animationOpts = {
|
|
curve = mkOption {
|
|
type = types.ints.between 0 40;
|
|
default = 38;
|
|
example = 19;
|
|
description = ''
|
|
The qt easing-curve animation to use for the animation. See
|
|
[
|
|
QEasingCurve documentation](https://doc.qt.io/qt-5/qeasingcurve.html#Type-enum).
|
|
'';
|
|
};
|
|
|
|
duration = mkOption {
|
|
type = types.ints.unsigned;
|
|
default = 1000;
|
|
example = 618;
|
|
description = "The animation duration in milliseconds.";
|
|
};
|
|
};
|
|
|
|
in
|
|
{
|
|
meta.maintainers = [ lib.hm.maintainers.austreelis ];
|
|
|
|
options.services.twmn = {
|
|
enable = mkEnableOption "twmn, a tiling window manager notification daemon";
|
|
|
|
duration = mkOption {
|
|
type = types.ints.unsigned;
|
|
default = 3000;
|
|
example = 5000;
|
|
description = ''
|
|
The time each notification remains visible, in milliseconds.
|
|
'';
|
|
};
|
|
|
|
extraConfig = mkOption {
|
|
type = types.attrs;
|
|
default = { };
|
|
example = literalExpression ''{ main.activation_command = "''${pkgs.hello}/bin/hello"; }'';
|
|
description = ''
|
|
Extra configuration options to add to the twmnd config file. See
|
|
<https://github.com/sboli/twmn/blob/master/README.md>
|
|
for details.
|
|
'';
|
|
};
|
|
|
|
host = mkOption {
|
|
type = types.str;
|
|
default = "127.0.0.1";
|
|
example = "laptop.lan";
|
|
description = "Host address to listen on for notifications.";
|
|
};
|
|
|
|
icons = {
|
|
critical = mkOption {
|
|
type = types.nullOr types.path;
|
|
default = null;
|
|
description = "Path to the critical notifications' icon.";
|
|
};
|
|
|
|
info = mkOption {
|
|
type = types.nullOr types.path;
|
|
default = null;
|
|
description = "Path to the informative notifications' icon.";
|
|
};
|
|
|
|
warning = mkOption {
|
|
type = types.nullOr types.path;
|
|
default = null;
|
|
description = "Path to the warning notifications' icon.";
|
|
};
|
|
};
|
|
|
|
port = mkOption {
|
|
type = types.port;
|
|
default = 9797;
|
|
description = "UDP port to listen on for notifications.";
|
|
};
|
|
|
|
screen = mkOption {
|
|
type = types.nullOr types.int;
|
|
default = null;
|
|
example = 0;
|
|
description = ''
|
|
Screen number to display notifications on when using a multi-head
|
|
desktop.
|
|
'';
|
|
};
|
|
|
|
soundCommand = mkOption {
|
|
type = types.str;
|
|
default = "";
|
|
description = "Command to execute to play a notification's sound.";
|
|
};
|
|
|
|
text = {
|
|
color = mkOption {
|
|
type = types.str;
|
|
default = "#999999";
|
|
example = "lightgray";
|
|
description = ''
|
|
Notification's text color. RGB hex and keywords (e.g. `lightgray`)
|
|
are supported.
|
|
'';
|
|
};
|
|
|
|
font = {
|
|
package = lib.mkPackageOption pkgs "font" {
|
|
default = null;
|
|
example = "pkgs.dejavu_fonts";
|
|
nullable = true;
|
|
extraDescription = ''
|
|
Package providing the font to use for the notification text.
|
|
This package is only used if `font.package` is not null.
|
|
'';
|
|
};
|
|
|
|
family = mkOption {
|
|
type = types.str;
|
|
default = "Sans";
|
|
example = "Noto Sans";
|
|
description = "Notification text's font family.";
|
|
};
|
|
|
|
size = mkOption {
|
|
type = types.ints.unsigned;
|
|
default = 13;
|
|
example = 42;
|
|
description = "Notification text's font size.";
|
|
};
|
|
|
|
variant = mkOption {
|
|
# These are the font variant supported by twmn
|
|
# See https://github.com/sboli/twmn/blob/master/README.md?plain=1#L42
|
|
type = types.enum [
|
|
"oblique"
|
|
"italic"
|
|
"ultra-light"
|
|
"light"
|
|
"medium"
|
|
"semi-bold"
|
|
"bold"
|
|
"ultra-bold"
|
|
"heavy"
|
|
"ultra-condensed"
|
|
"extra-condensed"
|
|
"condensed"
|
|
"semi-condensed"
|
|
"semi-expanded"
|
|
"expanded"
|
|
"extra-expanded"
|
|
"ultra-expanded"
|
|
];
|
|
default = "medium";
|
|
example = "heavy";
|
|
description = "Notification text's font variant.";
|
|
};
|
|
};
|
|
|
|
maxLength = mkOption {
|
|
type = types.nullOr types.ints.unsigned;
|
|
default = null;
|
|
example = 80;
|
|
description = ''
|
|
Maximum length of the text before it is cut and suffixed with "...".
|
|
Never cuts if `null`.
|
|
'';
|
|
};
|
|
};
|
|
|
|
window = {
|
|
alwaysOnTop = mkEnableOption "forcing the notification window to always be on top";
|
|
|
|
animation = {
|
|
easeIn = mkOption {
|
|
type = types.submodule { options = animationOpts; };
|
|
default = { };
|
|
example = {
|
|
curve = 19;
|
|
duration = 618;
|
|
};
|
|
description = "Options for the notification appearance's animation.";
|
|
};
|
|
|
|
easeOut = mkOption {
|
|
type = types.submodule { options = animationOpts; };
|
|
default = { };
|
|
example = {
|
|
curve = 19;
|
|
duration = 618;
|
|
};
|
|
description = "Options for the notification disappearance's animation.";
|
|
};
|
|
|
|
bounce = {
|
|
enable = mkEnableOption "notification bounce when displaying next notification directly";
|
|
|
|
duration = mkOption {
|
|
type = types.ints.unsigned;
|
|
default = 500;
|
|
example = 618;
|
|
description = "The bounce animation duration in milliseconds.";
|
|
};
|
|
};
|
|
};
|
|
|
|
color = mkOption {
|
|
type = types.str;
|
|
default = "#000000";
|
|
example = "lightgray";
|
|
description = ''
|
|
Notification's background color. RGB hex and keywords (e.g.
|
|
`lightgray`) are supported.
|
|
'';
|
|
};
|
|
|
|
height = mkOption {
|
|
type = types.ints.unsigned;
|
|
default = 18;
|
|
example = 42;
|
|
description = ''
|
|
Height of the slide bar. Useful to match your tiling window
|
|
manager's bar.
|
|
'';
|
|
};
|
|
|
|
offset = {
|
|
x = mkOption {
|
|
type = types.int;
|
|
default = 0;
|
|
example = 50;
|
|
description = ''
|
|
Offset of the notification's slide starting point in pixels on the
|
|
horizontal axis (positive is rightward).
|
|
'';
|
|
};
|
|
|
|
y = mkOption {
|
|
type = types.int;
|
|
default = 0;
|
|
example = -100;
|
|
description = ''
|
|
Offset of the notification's slide starting point in pixels on the
|
|
vertical axis (positive is upward).
|
|
'';
|
|
};
|
|
};
|
|
|
|
opacity = mkOption {
|
|
type = types.ints.between 0 100;
|
|
default = 100;
|
|
example = 80;
|
|
description = "The notification window's opacity.";
|
|
};
|
|
|
|
position = mkOption {
|
|
type = types.enum [
|
|
"tr"
|
|
"top_right"
|
|
"tl"
|
|
"top_left"
|
|
"br"
|
|
"bottom_right"
|
|
"bl"
|
|
"bottom_left"
|
|
"tc"
|
|
"top_center"
|
|
"bc"
|
|
"bottom_center"
|
|
"c"
|
|
"center"
|
|
];
|
|
default = "top_right";
|
|
example = "bottom_left";
|
|
description = ''
|
|
Position of the notification slide. The notification will slide
|
|
in vertically from the border if placed in
|
|
`top_center` or `bottom_center`,
|
|
horizontally otherwise.
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
|
|
#################
|
|
# Implementation
|
|
|
|
config = lib.mkIf cfg.enable {
|
|
assertions = [
|
|
(lib.hm.assertions.assertPlatform "services.twmn" pkgs lib.platforms.linux)
|
|
];
|
|
|
|
home.packages = lib.optional (!isNull cfg.text.font.package) cfg.text.font.package ++ [ pkgs.twmn ];
|
|
|
|
xdg.configFile."twmn/twmn.conf".text =
|
|
let
|
|
conf = lib.recursiveUpdate {
|
|
gui = {
|
|
always_on_top = if cfg.window.alwaysOnTop then "true" else "false";
|
|
background_color = cfg.window.color;
|
|
bounce = if cfg.window.animation.bounce.enable then "true" else "false";
|
|
bounce_duration = toString cfg.window.animation.bounce.duration;
|
|
font = cfg.text.font.family;
|
|
font_size = toString cfg.text.font.size;
|
|
font_variant = cfg.text.font.variant;
|
|
foreground_color = cfg.text.color;
|
|
height = toString cfg.window.height;
|
|
in_animation = toString cfg.window.animation.easeIn.curve;
|
|
in_animation_duration = toString cfg.window.animation.easeIn.duration;
|
|
max_length = toString (if isNull cfg.text.maxLength then -1 else cfg.text.maxLength);
|
|
offset_x = with cfg.window.offset; if x < 0 then toString x else "+${toString x}";
|
|
offset_y = with cfg.window.offset; if y < 0 then toString y else "+${toString y}";
|
|
opacity = toString cfg.window.opacity;
|
|
out_animation = toString cfg.window.animation.easeOut.curve;
|
|
out_animation_duration = toString cfg.window.animation.easeOut.duration;
|
|
inherit (cfg.window) position;
|
|
screen = toString cfg.screen;
|
|
};
|
|
# map null values to empty strings because formats.toml generator fails
|
|
# when encountering a null.
|
|
icons = lib.mapAttrs (_: toString) cfg.icons;
|
|
main = {
|
|
duration = toString cfg.duration;
|
|
inherit (cfg) host;
|
|
port = toString cfg.port;
|
|
sound_command = cfg.soundCommand;
|
|
};
|
|
} cfg.extraConfig;
|
|
|
|
mkLine = name: value: "${name}=${value}";
|
|
|
|
mkSection = section: conf: ''
|
|
[${section}]
|
|
${lib.concatStringsSep "\n" (lib.mapAttrsToList mkLine conf)}
|
|
'';
|
|
in
|
|
lib.concatStringsSep "\n" (lib.mapAttrsToList mkSection conf) + "\n";
|
|
|
|
systemd.user.services.twmnd = {
|
|
Unit = {
|
|
Description = "twmn daemon";
|
|
After = [ "graphical-session.target" ];
|
|
PartOf = [ "graphical-session.target" ];
|
|
X-Restart-Triggers = [ "${config.xdg.configFile."twmn/twmn.conf".source}" ];
|
|
};
|
|
|
|
Install.WantedBy = [ "graphical-session.target" ];
|
|
|
|
Service = {
|
|
ExecStart = "${pkgs.twmn}/bin/twmnd";
|
|
Restart = "on-failure";
|
|
Type = "simple";
|
|
StandardOutput = "null";
|
|
};
|
|
};
|
|
};
|
|
}
|