diff --git a/modules/config/home-manager.nix b/modules/config/home-manager.nix index d98126e..f93742e 100644 --- a/modules/config/home-manager.nix +++ b/modules/config/home-manager.nix @@ -12,6 +12,7 @@ home.stateVersion = stateVersion; } inputs.nix-index-database.hmModules.nix-index + inputs.wired-notify.homeManagerModules.default ]; }; # HM zsh needs this or else the startup order is fucked diff --git a/modules/optional/streamdeck.nix b/modules/optional/streamdeck.nix index e1ecd40..d2f48cc 100644 --- a/modules/optional/streamdeck.nix +++ b/modules/optional/streamdeck.nix @@ -31,11 +31,11 @@ in { Unit = { Description = "start streamdeck-ui"; # For some reason this depends on X or wayland running - ConditionEnvironment = ["DISPLAY" "WAYLAND_DISPLAYS"]; + ConditionEnvironment = ["DISPLAY"]; }; Service = { Type = "exec"; - ExecStart = "${pkgs.streamdeck-ui}/bin/streamdeck-ui --no-ui"; + ExecStart = "${pkgs.streamdeck-ui}/bin/streamdeck --no-ui"; Environment = "STREAMDECK_UI_CONFIG=${config.xdg.configHome}/streamdeck-ui/config.json"; }; Install.WantedBy = ["graphical-session.target"]; diff --git a/users/common/graphical/default.nix b/users/common/graphical/default.nix index ec965fa..f19fed3 100644 --- a/users/common/graphical/default.nix +++ b/users/common/graphical/default.nix @@ -6,6 +6,7 @@ imports = [ #./deadd ./themes.nix + #./wired-notify.nix ]; home = { packages = with pkgs; [ @@ -17,13 +18,13 @@ }; # notification are nice to have - services.dunst = { - enable = true; - settings.global = { - frame_width = 0; - highlight = config.lib.stylix.colors.withHashtag.base0C; - progress_bar_frame_width = 0; - progress_bar_corner_radius = 0; - }; - }; + #services.dunst = { + # enable = true; + # settings.global = { + # frame_width = 0; + # highlight = config.lib.stylix.colors.withHashtag.base0C; + # progress_bar_frame_width = 0; + # progress_bar_corner_radius = 0; + # }; + #}; } diff --git a/users/common/graphical/sway3.nix b/users/common/graphical/sway3.nix index 5c38ccf..aa327f7 100644 --- a/users/common/graphical/sway3.nix +++ b/users/common/graphical/sway3.nix @@ -172,8 +172,8 @@ in { # icons = "material-nf"; settings = { icons.icons = "material-nf"; - "icons.overrides" = { - cpu = ""; + icons.overrides = { + cpu = " "; }; }; }; diff --git a/users/common/graphical/wired-notify.nix b/users/common/graphical/wired-notify.nix new file mode 100644 index 0000000..814d2e4 --- /dev/null +++ b/users/common/graphical/wired-notify.nix @@ -0,0 +1,430 @@ +{ + config, + lib, + pkgs, + ... +}: { + systemd.user.services.wired.Unit.ConditionEnvironment = "DISPLAY"; + services.wired = { + enable = true; + config = let + inherit (builtins) floor; + + format = pkgs.formats.ron {}; + inherit (format.lib) mkLiteral struct; + + colors = lib.mapAttrs (_: Color) config.lib.stylix.colors.withHashtag; + inherit (config.stylix) fonts; + + # A global scaling factor to apply to all notifications. + globalScale = 1.2; + + # Ron format shorthands and helpers + unnamedStruct = struct ""; + Color = hex: struct "Color" {inherit hex;}; + Hook = struct "Hook"; + Vec2 = x: y: struct "Vec2" {inherit x y;}; + ShortcutsConfig = struct "ShortcutsConfig"; + And = struct "And"; + Or = struct "Or"; + Not = struct "Not"; + + mkVec2 = x: y: Vec2 (globalScale * x) (globalScale * y); + + mkHook = parent_anchor: self_anchor: + Hook { + parent_anchor = mkLiteral parent_anchor; + self_anchor = mkLiteral self_anchor; + }; + + mkPaddingLrBt = left: right: bottom: top: + struct "Padding" { + left = globalScale * left; + right = globalScale * right; + bottom = globalScale * bottom; + top = globalScale * top; + }; + + mkDimensionsWH = minw: maxw: minh: maxh: + unnamedStruct { + width = unnamedStruct { + min = floor (globalScale * minw); + max = floor (globalScale * maxw); + }; + height = unnamedStruct { + min = floor (globalScale * minh); + max = floor (globalScale * maxh); + }; + }; + + # Reusable blocks + mkRootBlock = name: { + name = "${name}_root"; + parent = ""; + hook = mkHook "TR" "TR"; + offset = Vec2 (-50) 50; # Vec2 instead of mkVec2 to not apply scaling. + params = struct "NotificationBlock" (unnamedStruct { + monitor = 0; + border_width = globalScale * 2; + border_rounding = globalScale * 0; + background_color = colors.base00; + border_color = colors.base04; + border_color_low = colors.base04; + border_color_critical = colors.base08; + border_color_paused = colors.base09; + gap = mkVec2 0 8; + notification_hook = mkHook "BR" "TR"; + }); + }; + + mkTopBar = name: extra: + map (x: extra // x) [ + { + name = "${name}_app_image"; + parent = "${name}_root"; + hook = mkHook "TL" "TL"; + offset = mkVec2 0 0; + params = struct "ImageBlock" (unnamedStruct { + filter_mode = mkLiteral "Lanczos3"; + image_type = mkLiteral "App"; + min_height = floor (globalScale * 24); + min_width = floor (globalScale * 24); + padding = mkPaddingLrBt 16 (-8) 6 12; + rounding = globalScale * 12.0; + scale_height = floor (globalScale * 24); + scale_width = floor (globalScale * 24); + }); + } + { + name = "${name}_app_name"; + parent = "${name}_app_image"; + hook = mkHook "TR" "TL"; + offset = mkVec2 0 0; + params = struct "TextBlock" (unnamedStruct { + color = colors.base05; + dimensions = mkDimensionsWH 350 350 28 28; + ellipsize = mkLiteral "End"; + font = "${fonts.monospace.name} ${toString (globalScale * 14)}"; + padding = mkPaddingLrBt 16 0 0 12; + text = "%n"; + }); + } + ]; + + mkBody = name: ident: yOffset: summaryRightPadding: extra: let + maxWFull = 580 - 12; + maxWImg = maxWFull - 128 - 12; + in + map (x: extra // x) ( + [ + { + name = "${name}_${ident}_hint"; + parent = "${name}_root"; + hook = mkHook "TL" "TL"; + offset = mkVec2 0 yOffset; + params = struct "ImageBlock" (unnamedStruct { + filter_mode = mkLiteral "Lanczos3"; + image_type = mkLiteral "Hint"; + padding = mkPaddingLrBt 12 0 12 12; + rounding = globalScale * 9; + scale_height = floor (globalScale * 128); + scale_width = floor (globalScale * 128); + }); + } + { + name = "${name}_${ident}_summary"; + parent = "${name}_${ident}_hint"; + hook = mkHook "TR" "TL"; + offset = mkVec2 0 0; + params = struct "TextBlock" (unnamedStruct { + text = "%s"; + font = "${fonts.sansSerif.name} Bold ${toString (globalScale * 16)}"; + ellipsize = mkLiteral "End"; + color = colors.base06; + padding = mkPaddingLrBt 16 16 0 8; + dimensions = mkDimensionsWH (maxWFull - summaryRightPadding) (maxWFull - summaryRightPadding) 0 30; + dimensions_image_hint = mkDimensionsWH (maxWImg - summaryRightPadding) (maxWImg - summaryRightPadding) 0 30; + dimensions_image_both = mkDimensionsWH (maxWImg - summaryRightPadding) (maxWImg - summaryRightPadding) 0 30; + }); + } + { + name = "${name}_${ident}_body"; + parent = "${name}_${ident}_summary"; + hook = mkHook "BL" "TL"; + offset = mkVec2 0 12; + render_criteria = [ + (And [ + (Or extra.render_criteria) + (mkLiteral "Body") + ]) + ]; + params = struct "TextBlock" (unnamedStruct { + text = "%b"; + font = "${fonts.sansSerif.name} ${toString (globalScale * 16)}"; + ellipsize = mkLiteral "End"; + color = colors.base06; + padding = mkPaddingLrBt 16 16 12 0; + dimensions = mkDimensionsWH maxWFull maxWFull 0 88; + dimensions_image_hint = mkDimensionsWH maxWImg maxWImg 0 88; + dimensions_image_both = mkDimensionsWH maxWImg maxWImg 0 88; + }); + } + ] + # We unfortunately cannot move these out of mkBody, because the depend + # on the specific name for the parent, which cannot be changed dynamically. + # So each call to mkBody creates these progress bars which only differ in + # their parent :/ + ++ (mkProgress name "${ident}_hint" 0 { + render_criteria = [ + (And (extra.render_criteria + ++ [ + (mkLiteral "Progress") + (mkLiteral "HintImage") + ])) + ]; + }) + ++ (mkProgress name "${ident}_body" 0 { + render_criteria = [ + (And (extra.render_criteria + ++ [ + (mkLiteral "Progress") + (mkLiteral "Body") + (Not (mkLiteral "HintImage")) + ])) + ]; + }) + ++ (mkProgress name "${ident}_summary" 9 { + render_criteria = [ + (And (extra.render_criteria + ++ [ + (mkLiteral "Progress") + (mkLiteral "Summary") + (Not (Or [ + (mkLiteral "Body") + (mkLiteral "HintImage") + ])) + ])) + ]; + }) + # Each mkProgress includes a button bar. But if no progress is included in a notification, + # those won't be rendered, so we have to define bars for non-progress notifications. + # (And yes, we need 3 because we cannot have duplicate names or dynamic parents) + ++ (mkButtonBar name "${ident}_hint" 0 { + render_criteria = [ + (And (extra.render_criteria + ++ [ + (Not (mkLiteral "Progress")) + (mkLiteral "HintImage") + ])) + ]; + }) + ++ (mkButtonBar name "${ident}_body" (-8) { + render_criteria = [ + (And (extra.render_criteria + ++ [ + (Not (mkLiteral "Progress")) + (mkLiteral "Body") + (Not (mkLiteral "HintImage")) + ])) + ]; + }) + ++ (mkButtonBar name "${ident}_summary" 0 { + render_criteria = [ + (And (extra.render_criteria + ++ [ + (Not (mkLiteral "Progress")) + (mkLiteral "Summary") + (Not (Or [ + (mkLiteral "Body") + (mkLiteral "HintImage") + ])) + ])) + ]; + }) + ); + + mkProgress = name: parent: yOffset: extra: + map (x: extra // x) ( + [ + { + name = "${name}_progress_${parent}_text"; + parent = "${name}_${parent}"; + hook = mkHook "BL" "TL"; + offset = mkVec2 0 0; + params = struct "TextBlock" (unnamedStruct { + text = "%p%"; + font = "${fonts.monospace.name} Bold ${toString (globalScale * 14)}"; + align = mkLiteral "Right"; + ellipsize = mkLiteral "End"; + color = colors.base06; + padding = mkPaddingLrBt 12 16 12 yOffset; + dimensions = mkDimensionsWH 48 48 24 24; + }); + } + { + name = "${name}_progress_${parent}_bar"; + parent = "${name}_${parent}"; + hook = mkHook "BL" "TL"; + offset = mkVec2 0 0; + params = struct "ProgressBlock" (unnamedStruct { + width = globalScale * 510; + height = globalScale * 12; + border_width = 0.0; + border_rounding = globalScale * 6; + border_color = colors.base03; + background_color = colors.base03; + fill_color = colors.base0D; + fill_rounding = globalScale * 6; + padding = mkPaddingLrBt 68 16 12 (yOffset + 8); + }); + } + ] + ++ (mkButtonBar name "progress_${parent}_text" ( + /* + ignore bottom end padding + */ + -8 + ) + extra) + ); + + mkButtonBar = name: parent: yOffset: extra: + map (x: extra // x) (lib.flatten [ + { + name = "${name}_button_bar_for_${parent}"; + parent = "${name}_${parent}"; + hook = mkHook "BL" "TL"; + offset = mkVec2 0 yOffset; + render_criteria = [ + (And (extra.render_criteria + ++ [ + (Or [ + (struct "ActionOther" 0) + (struct "ActionOther" 1) + (struct "ActionOther" 2) + (struct "ActionOther" 3) + (struct "ActionOther" 4) + (struct "ActionOther" 5) + ]) + ])) + ]; + params = struct "TextBlock" (unnamedStruct { + text = ""; + font = "${fonts.monospace.name} ${toString (globalScale * 14)}"; + color = colors.base06; + padding = mkPaddingLrBt 0 0 0 0; + dimensions = mkDimensionsWH 568 568 56 48; + }); + } + (lib.flip map [0 1 2 3 4 5] ( + i: + lib.optionalAttrs (i == 0) { + parent = "${name}_button_bar_for_${parent}"; + hook = mkHook "TL" "TL"; + offset = mkVec2 16 12; + } + // lib.optionalAttrs (i != 0) { + parent = "${name}_action_${toString (i - 1)}_for_${parent}"; + hook = mkHook "TR" "TL"; + offset = mkVec2 8 0; + } + // { + name = "${name}_action_${toString i}_for_${parent}"; + render_criteria = [ + ( + And (extra.render_criteria + ++ [ + (struct "ActionOther" i) + ]) + ) + ]; + params = struct "ButtonBlock" (unnamedStruct { + text = "%a"; + font = "${fonts.monospace.name} Bold ${toString (globalScale * 14)}"; + ellipsize = mkLiteral "End"; + action = struct "OtherAction" i; + text_color = colors.base06; + text_color_hovered = colors.base06; + background_color = colors.base01; + background_color_hovered = colors.base02; + border_color = colors.base04; + border_color_hovered = colors.base0F; + border_rounding = globalScale * 0; + border_width = globalScale * 2; + padding = mkPaddingLrBt 8 8 4 4; + # Technically distribute like below, but we'll just allow more even + # if it breaks when having > 4 max length buttons, because it probably + # never happens and looks a lot better this way. + dimensions = mkDimensionsWH 32 144 24 24; + # dimensions = mkDimensionsWH 32 (( + # 568 /* available width */ + # - 2 * 16 /* padding lr */ + # - (/* count actions */ 6 - 1) * 8 /* padding between */ + # ) / /* count actions */ 6) 24 24; + }); + } + )) + ]); + in + format.generate "wired.ron" (unnamedStruct { + max_notifications = 10; + timeout = 10000; + poll_interval = 6; # 6ms ~= 166hz. + history_length = 60; + replacing_enabled = true; + replacing_resets_timeout = true; + min_window_width = floor (globalScale * 600); + min_window_height = floor (globalScale * 60); + debug = false; + + # https://github.com/Toqozz/wired-notify/wiki/Shortcuts + shortcuts = ShortcutsConfig { + notification_interact = 1; # left click + notification_pause = 1; + notification_close = 2; # right click + notification_action1 = 3; # middle click + }; + + layout_blocks = let + criterionHasTopBar = And [ + (mkLiteral "AppImage") + (Not (Or [ + (struct "AppName" "") + (struct "AppName" "notify-send") + ])) + ]; + in + map unnamedStruct (lib.flatten [ + (mkRootBlock "general") + # Time is always shown in the top right corner. + { + name = "general_time"; + parent = "general_root"; + hook = mkHook "TR" "TR"; + offset = mkVec2 0 0; + params = struct "TextBlock" (unnamedStruct { + color = colors.base05; + dimensions = mkDimensionsWH 100 100 28 28; + ellipsize = mkLiteral "End"; + font = "${fonts.monospace.name} Bold ${toString (globalScale * 14)}"; + padding = mkPaddingLrBt 0 16 4 12; + text = "%t(%a %H:%M)"; + }); + } + # Top bar for app image, name and time, but only + # if there is an app name or image. + (mkTopBar "general" { + render_criteria = [criterionHasTopBar]; + }) + # if no top bar present: A body with no offset and a summary padding to the right (to not overlay the time) + (mkBody "general" "notop" 0 (16 + 100) { + render_criteria = [(Not criterionHasTopBar)]; + }) + # if top bar present: A body with matching y offset and no summary padding to the right + (mkBody "general" "withtop" 36 0 { + render_criteria = [criterionHasTopBar]; + }) + ]); + }); + }; +} diff --git a/users/patrick/impermanence.nix b/users/patrick/impermanence.nix index ddbe67f..bc17038 100644 --- a/users/patrick/impermanence.nix +++ b/users/patrick/impermanence.nix @@ -30,6 +30,9 @@ # bottles state games ".local/share/bottles" + + ".config/spotify" + ".cache/spotify" ]; }; "/panzer/state".directories = diff --git a/users/patrick/patrick.nix b/users/patrick/patrick.nix index 33b44bd..761f4b0 100644 --- a/users/patrick/patrick.nix +++ b/users/patrick/patrick.nix @@ -11,6 +11,7 @@ signal-desktop telegram-desktop chromium + spotify ]; }; }