parent
11c0e5d188
commit
63dccc4e60
@ -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"; |
||||
}; |
||||
}; |
||||
}; |
||||
} |
@ -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 |
||||
|
@ -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} |
||||
''; |
||||
}; |
||||
} |
@ -0,0 +1 @@ |
||||
{ twmn-basic-configuration = ./basic-configuration.nix; } |
Loading…
Reference in new issue