depot/third_party/home-manager/modules/services/twmn.nix
Default email 75fa0ae5af Project import generated by Copybara.
GitOrigin-RevId: c3ab5ea047e6dc73df530948f7367455749d8906
2023-08-08 12:19:01 +02:00

380 lines
10 KiB
Nix

{ config, lib, pkgs, stdenv, ... }:
with lib;
let
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 = [ 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 = mkOption {
type = types.nullOr types.package;
default = null;
example = literalExpression "pkgs.dejavu_fonts";
description = ''
Notification text's font package. If `null` then
the font is assumed to already be available in your profile.
'';
};
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 = literalExpression ''
{
curve = 19;
duration = 618;
}
'';
description = "Options for the notification appearance's animation.";
};
easeOut = mkOption {
type = types.submodule { options = animationOpts; };
default = { };
example = literalExpression ''
{
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 = 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 = 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;
position = cfg.window.position;
screen = toString cfg.screen;
};
# map null values to empty strings because formats.toml generator fails
# when encountering a null.
icons = mapAttrs (_: toString) cfg.icons;
main = {
duration = toString cfg.duration;
host = cfg.host;
port = toString cfg.port;
sound_command = cfg.soundCommand;
};
} cfg.extraConfig;
mkLine = name: value: "${name}=${value}";
mkSection = section: conf: ''
[${section}]
${concatStringsSep "\n" (mapAttrsToList mkLine conf)}
'';
in concatStringsSep "\n" (mapAttrsToList mkSection conf) + "\n";
systemd.user.services.twmnd = {
Unit = {
Description = "twmn daemon";
After = [ "graphical-session-pre.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";
};
};
};
}