twmn: add module

This module allows to configure and start the twmn daemon.
This commit is contained in:
Morgane Austreelis 2021-10-24 15:50:13 +02:00 committed by Robert Helgesson
parent 11c0e5d188
commit 63dccc4e60
No known key found for this signature in database
GPG key ID: 36BDAA14C2797E89
9 changed files with 483 additions and 0 deletions

3
.github/CODEOWNERS vendored
View file

@ -370,6 +370,9 @@
/modules/services/trayer.nix @AndreasMager
/tests/modules/services/trayer @AndreasMager
/modules/services/twmn.nix @Austreelis
/tests/modules/services/twmn @Austreelis
/modules/services/udiskie.nix @rycee
/modules/services/unison.nix @pacien

View file

@ -13,6 +13,12 @@
github = "amesgen";
githubId = 15369874;
};
austreelis = {
email = "github@accounts.austreelis.net";
github = "Austreelis";
githubId = 56743515;
name = "Morgane Austreelis";
};
CarlosLoboxyz = {
name = "Carlos Lobo";
email = "86011416+CarlosLoboxyz@users.noreply.github.com";

View file

@ -2395,6 +2395,14 @@ in
A new module is available: 'programs.kodi'.
'';
}
{
time = "2022-02-03T23:23:49+00:00";
condition = hostPlatform.isLinux;
message = ''
A new module is available: 'services.twmn'.
'';
}
];
};
}

View file

@ -236,6 +236,7 @@ let
./services/tahoe-lafs.nix
./services/taskwarrior-sync.nix
./services/trayer.nix
./services/twmn.nix
./services/udiskie.nix
./services/unclutter.nix
./services/unison.nix

380
modules/services/twmn.nix Normal file
View file

@ -0,0 +1,380 @@
{ 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
<link xlink:href="https://doc.qt.io/qt-5/qeasingcurve.html#Type-enum">
QEasingCurve documentation</link>.
'';
};
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
<link xlink:href="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. <literal>lightgray</literal>)
are supported.
'';
};
font = {
package = mkOption {
type = types.nullOr types.package;
default = null;
example = literalExpression "pkgs.dejavu_fonts";
description = ''
Notification text's font package. If <literal>null</literal> 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 <literal>null</literal>.
'';
};
};
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.
<literal>lightgray</literal>) 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
<literal>top_center</literal> or <literal>bottom_center</literal>,
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";
};
};
};
}

View file

@ -156,6 +156,7 @@ import nmt {
./modules/services/sxhkd
./modules/services/syncthing
./modules/services/trayer
./modules/services/twmn
./modules/services/window-managers/bspwm
./modules/services/window-managers/herbstluftwm
./modules/services/window-managers/i3

View file

@ -0,0 +1,32 @@
[gui]
always_on_top=true
background_color=black
bounce=true
bounce_duration=271
font=Noto Sans
font_size=16
font_variant=italic
foreground_color=#FF00FF
height=20
in_animation=27
in_animation_duration=314
max_length=80
offset_x=+20
offset_y=-60
opacity=80
out_animation=13
out_animation_duration=168
position=center
screen=0
[icons]
critical=/path/icon/critical
info=/path/icon/info
warning=/path/icon/warning
[main]
duration=4242
host=example.com
port=9006
sound_command=/path/sound/command

View file

@ -0,0 +1,51 @@
{
config = {
services.twmn = {
enable = true;
duration = 4242;
host = "example.com";
port = 9006;
screen = 0;
soundCommand = "/path/sound/command";
icons.critical = "/path/icon/critical";
icons.info = "/path/icon/info";
icons.warning = "/path/icon/warning";
text = {
color = "#FF00FF";
font.family = "Noto Sans";
font.size = 16;
font.variant = "italic";
maxLength = 80;
};
window = {
alwaysOnTop = true;
color = "black";
height = 20;
offset.x = 20;
offset.y = -60;
opacity = 80;
position = "center";
animation = {
easeIn.curve = 27;
easeIn.duration = 314;
easeOut.curve = 13;
easeOut.duration = 168;
bounce.enable = true;
bounce.duration = 271;
};
};
};
test.stubs.twmn = { };
nmt.script = ''
serviceFile="home-files/.config/systemd/user/twmnd.service"
assertFileExists "$serviceFile"
assertFileRegex "$serviceFile" 'X-Restart-Triggers=.*twmn\.conf'
assertFileRegex "$serviceFile" 'ExecStart=@twmn@/bin/twmnd'
assertFileExists "home-files/.config/twmn/twmn.conf"
assertFileContent "home-files/.config/twmn/twmn.conf" \
${./basic-configuration.conf}
'';
};
}

View file

@ -0,0 +1 @@
{ twmn-basic-configuration = ./basic-configuration.nix; }