twmn: add module
This module allows to configure and start the twmn daemon.
This commit is contained in:
parent
11c0e5d188
commit
63dccc4e60
3
.github/CODEOWNERS
vendored
3
.github/CODEOWNERS
vendored
|
@ -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
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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'.
|
||||
'';
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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
380
modules/services/twmn.nix
Normal 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";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -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
|
||||
|
|
32
tests/modules/services/twmn/basic-configuration.conf
Normal file
32
tests/modules/services/twmn/basic-configuration.conf
Normal 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
|
||||
|
51
tests/modules/services/twmn/basic-configuration.nix
Normal file
51
tests/modules/services/twmn/basic-configuration.nix
Normal 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}
|
||||
'';
|
||||
};
|
||||
}
|
1
tests/modules/services/twmn/default.nix
Normal file
1
tests/modules/services/twmn/default.nix
Normal file
|
@ -0,0 +1 @@
|
|||
{ twmn-basic-configuration = ./basic-configuration.nix; }
|
Loading…
Reference in a new issue