2021-10-24 13:50:13 +00:00
|
|
|
{ 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
|
2023-06-30 23:06:56 +00:00
|
|
|
"notification bounce when displaying next notification directly";
|
2021-10-24 13:50:13 +00:00
|
|
|
|
|
|
|
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";
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}
|