diff --git a/home/Kell.nix b/home/Kell.nix index 0196ce3..244c720 100644 --- a/home/Kell.nix +++ b/home/Kell.nix @@ -17,7 +17,7 @@ }; programs.waybar.settings.mainBar = { output = "DP-2"; - "custom/temperature".exec = "${pkgs.lm_sensors}/bin/sensors | ${pkgs.gnugrep}/bin/grep Tctl | cut -c16-22"; + "custom/temperature".exec = "sensors | grep Tctl | cut -c16-22"; }; wayland.windowManager.hyprland.settings = { monitor = [ diff --git a/home/ags/files/config.js b/home/ags/files/config.js index 611577e..b9210a2 100644 --- a/home/ags/files/config.js +++ b/home/ags/files/config.js @@ -1,14 +1,13 @@ const hyprland = await Service.import("hyprland") -const notifications = await Service.import("notifications") -const mpris = await Service.import("mpris") const audio = await Service.import("audio") const battery = await Service.import("battery") const systemtray = await Service.import("systemtray") import { getIconName } from "./utils.js" +import { NotificationPopups } from "./notificationPopups.js" const volumeIndicator = Widget.Button({ on_clicked: () => audio.speaker.is_muted = !audio.speaker.is_muted, - class_name: "item", + class_name: "item blue", child: Widget.Box({ children: [ Widget.Icon().hook(audio.speaker, self => { @@ -25,8 +24,9 @@ const volumeIndicator = Widget.Button({ self.tooltip_text = `Volume ${Math.floor(vol)}%` }), Widget.Label().hook(audio.speaker, self => { - self.label = `${Math.floor(audio.speaker.volume * 100)}%` - self.css = "margin-left: 0.7em;" + const label = `${Math.floor(audio.speaker.volume * 100)}%` + self.label = audio.speaker.is_muted ? "" : label + self.css = audio.speaker.is_muted ? "margin-left:0;": "margin-left: 0.7em;" }) ]}), }); @@ -34,33 +34,30 @@ const volumeIndicator = Widget.Button({ function Clients() { const activeId = hyprland.active.client.bind("address") const clients = hyprland.bind("clients").as(cl => - cl.map(({ address, title, workspace }) => + cl.filter(a => !a.title.includes("rofi")).map(({ address, title, workspace }) => { const short_title = title.length > 40 ? title.slice(0, 20) + "..." : title - return Widget.Button({ + return Widget.Box({ attribute: workspace.id, - child: Widget.Box({ - children: [ - Widget.Icon({ - vexpand: false, - size: 16, - className: "app-icon", - icon: getIconName(hyprland.clients.find(c => c.address === address)) - }), - Widget.Label(`${short_title}`) - ], - }), - class_name: activeId.as(i => `${i === address ? "item client-title focused" : "item client-title"}`), + class_name: activeId.as(i => `${i === address ? "focused" : ""}`), + children: [ + Widget.Icon({ + vexpand: false, + size: 16, + className: "app-icon", + icon: getIconName(hyprland.clients.find(c => c.address === address)) + }), + Widget.Label(`${short_title}`) + ], }) - } - ) + }) ) return Widget.Box({ class_name: "clients", children: clients, - setup: self => self.hook(hyprland, () => self.children.forEach(btn => { - btn.visible = hyprland.active.workspace.id === btn.attribute + setup: self => self.hook(hyprland, () => self.children.forEach(box => { + box.visible = hyprland.active.workspace.id === box.attribute })), }) } @@ -88,28 +85,12 @@ function Workspaces() { }) } -function Notification() { - const popups = notifications.bind("popups") - return Widget.Box({ - class_name: "notification", - visible: popups.as(p => p.length > 0), - children: [ - Widget.Icon({ - icon: "preferences-system-notifications-symbolic", - }), - Widget.Label({ - label: popups.as(p => p[0]?.summary || ""), - }), - ], - }) -} - function SysTray() { const items = systemtray.bind("items") .as(items => items.map(item => Widget.Button({ child: Widget.Icon({ icon: item.bind("icon"), - css: "margin-left: 0.6em;margin-right: 0.6em;" + css: "margin-left: 0.3em;margin-right: 0.3em;" }), on_primary_click: (_, event) => item.activate(event), on_secondary_click: (_, event) => item.openMenu(event), @@ -136,7 +117,6 @@ function Center() { spacing: 8, children: [ Workspaces(), - Notification(), ], }) } @@ -145,15 +125,32 @@ function Right() { return Widget.Box({ hpack: "end", spacing: 8, + class_name: "right", children: [ SysTray(), volumeIndicator, Widget.Label({ class_name: "item", - label: Variable("", { poll: [1000, 'date "+%Y-%m-%d"'] }).bind(), + label: Variable("", { poll: [5000, 'bash -c "top -bn1 | grep \\"Cpu(s)\\" | sed \\"s/.*, *\\([0-9.]*\\)%* id.*/\\\\1/\\" | awk \'{print \\"CPU \\" 100 - \\$1 \\"%\\"}\'"'] }).bind(), + }), + Widget.Label({ + class_name: "item blue", + label: Variable("", { poll: [5000, 'bash -c "cat /proc/cpuinfo | grep \\"MHz\\" | awk \'{print \\$4}\' | sort -n | tail -1 | awk \'{printf \\"%.2f GHz\\", \\$1/1000}\'"'] }).bind(), }), Widget.Label({ class_name: "item", + label: Variable("", { poll: [5000, 'bash -c \'free -m | awk \'\\\'\'/^Mem/ {printf "%.2f GB\\n", $3/1024}\'\\\''] }).bind(), + }), + Widget.Label({ + class_name: "item blue", + label: Variable("", { poll: [5000, 'bash -c "sensors | grep Tctl | cut -c16-22"'] }).bind(), + }), + Widget.Label({ + class_name: "item", + label: Variable("", { poll: [1000, 'date "+%Y-%m-%d"'] }).bind(), + }), + Widget.Label({ + class_name: "item blue", label: Variable("", { poll: [1000, 'date "+%H:%M:%S"'] }).bind(), }) ], @@ -162,7 +159,7 @@ function Right() { function Bar(monitor = 0) { return Widget.Window({ - name: `bar-${monitor}`, // name has to be unique + name: `ags-bar-${monitor}`, // name has to be unique class_name: "bar", monitor, anchor: ["top", "left", "right"], @@ -179,7 +176,8 @@ function Bar(monitor = 0) { App.config({ style: "./style.css", windows: [ - Bar(1), + Bar(0), + NotificationPopups(), ], }) diff --git a/home/ags/files/notificationPopups.js b/home/ags/files/notificationPopups.js new file mode 100644 index 0000000..f360d3c --- /dev/null +++ b/home/ags/files/notificationPopups.js @@ -0,0 +1,130 @@ +const notifications = await Service.import("notifications") + +/** @param {import('resource:///com/github/Aylur/ags/service/notifications.js').Notification} n */ +function NotificationIcon({ app_entry, app_icon, image }) { + if (image) { + return Widget.Box({ + css: `background-image: url("${image}");` + + "background-size: contain;" + + "background-repeat: no-repeat;" + + "background-position: center;", + }) + } + + let icon = "dialog-information-symbolic" + if (Utils.lookUpIcon(app_icon)) + icon = app_icon + + if (app_entry && Utils.lookUpIcon(app_entry)) + icon = app_entry + + return Widget.Box({ + child: Widget.Icon(icon), + }) +} + +/** @param {import('resource:///com/github/Aylur/ags/service/notifications.js').Notification} n */ +function Notification(n) { + const icon = Widget.Box({ + vpack: "start", + class_name: "icon", + child: NotificationIcon(n), + }) + + const title = Widget.Label({ + class_name: "title", + xalign: 0, + justification: "left", + hexpand: true, + max_width_chars: 24, + truncate: "end", + wrap: true, + label: n.summary, + use_markup: true, + }) + + const body = Widget.Label({ + class_name: "body", + hexpand: true, + use_markup: true, + xalign: 0, + justification: "left", + label: n.body, + wrap: true, + }) + + const actions = Widget.Box({ + class_name: "actions", + children: n.actions.map(({ id, label }) => Widget.Button({ + class_name: "action-button", + on_clicked: () => { + n.invoke(id) + n.dismiss() + }, + hexpand: true, + child: Widget.Label(label), + })), + }) + + return Widget.EventBox( + { + attribute: { id: n.id }, + on_primary_click: n.dismiss, + }, + Widget.Box( + { + class_name: `notification ${n.urgency}`, + vertical: true, + }, + Widget.Box([ + icon, + Widget.Box( + { vertical: true }, + title, + body, + ), + ]), + actions, + ), + ) +} + +export function NotificationPopups(monitor = 0) { + const list = Widget.Box({ + vertical: true, + children: notifications.popups.map(Notification), + }) + + function onNotified(_, /** @type {number} */ id) { + const n = notifications.getNotification(id) + if (n) + list.children = [Notification(n), ...list.children] + } + + function onDismissed(_, /** @type {number} */ id) { + list.children.find(n => n.attribute.id === id)?.destroy() + } + + list.hook(notifications, onNotified, "notified") + .hook(notifications, onDismissed, "dismissed") + + return Widget.Window({ + monitor, + name: `notifications${monitor}`, + class_name: "notification-popups", + anchor: ["top", "right"], + child: Widget.Box({ + css: "min-width: 2px; min-height: 2px;", + class_name: "notifications", + vertical: true, + child: list, + + /** this is a simple one liner that could be used instead of + hooking into the 'notified' and 'dismissed' signals. + but its not very optimized becuase it will recreate + the whole list everytime a notification is added or dismissed */ + // children: notifications.bind('popups') + // .as(popups => popups.map(Notification)) + }), + }) +} diff --git a/home/ags/files/style.css b/home/ags/files/style.css index aa85e6c..8765cc5 100644 --- a/home/ags/files/style.css +++ b/home/ags/files/style.css @@ -8,11 +8,11 @@ window.bar { margin-top: 0.2em; } -.client-title { +.clients box { margin-right: 0.3em; } -.item { +.item, .clients box { background: #1f2430; padding-left: 0.7em; padding-right: 0.7em; @@ -30,12 +30,15 @@ button { border-radius: 0.3em; } -button.focused, button:hover { +.focused, .clients box.focused { + background: #023269; +} + +button:hover { background: #023269; } .workspaces button { - font-weight: bold; padding-left: 0.4em; padding-right: 0.4em; margin-left: 0.2em; @@ -45,3 +48,66 @@ button.focused, button:hover { .notification { color: yellow; } + +.blue { + background: #023269; +} + +/* Notifications */ +window.notification-popups box.notifications { + padding: .5em; +} + +.icon { + min-width: 68px; + min-height: 68px; + margin-right: 1em; +} + +.icon image { + font-size: 58px; + /* to center the icon */ + margin: 5px; + color: @theme_fg_color; +} + +.icon box { + min-width: 68px; + min-height: 68px; + border-radius: 7px; +} + +.notification { + min-width: 350px; + border-radius: 11px; + padding: 1em; + margin: .5em; + border: 1px solid @wm_borders_edge; + background-color: @theme_bg_color; +} + +.notification.critical { + border: 1px solid lightcoral; +} + +.title { + color: @theme_fg_color; + font-size: 1.4em; +} + +.body { + color: @theme_unfocused_fg_color; +} + +.actions .action-button { + margin: 0 .4em; + margin-top: .8em; +} + +.actions .action-button:first-child { + margin-left: 0; +} + +.actions .action-button:last-child { + margin-right: 0; +} diff --git a/home/hyprland/default.nix b/home/hyprland/default.nix index 9abbfb5..80b7c28 100644 --- a/home/hyprland/default.nix +++ b/home/hyprland/default.nix @@ -2,9 +2,9 @@ let playerctl = "${pkgs.playerctl}/bin/playerctl"; wl-paste = "${pkgs.wl-clipboard}/bin/wl-paste"; + ags = "${pkgs.ags}/bin/ags"; nautilus = "${pkgs.nautilus}/bin/nautilus"; gnome-calendar = "${pkgs.gnome-calendar}/bin/gnome-calendar"; - waybar = "${inputs.waybar.packages.${pkgs.system}.waybar}/bin/waybar"; wpctl = "${pkgs.wireplumber}/bin/wpctl"; alacritty = "${pkgs.alacritty}/bin/alacritty"; swaylock = "${pkgs.swaylock-fancy}/bin/swaylock-fancy"; @@ -90,6 +90,10 @@ in { services.dunst = { enable = true; + iconTheme = { + package = pkgs.gnome3.adwaita-icon-theme; + name = "Adwaita"; + }; settings = { global = { frame_color = "#00000000"; @@ -118,7 +122,7 @@ in { "$mainMod" = "SUPER"; exec-once = [ "hyprpaper" - "${waybar}" + "${ags}" "${wl-paste} --type text --watch cliphist store" "${wl-paste} --type image --watch cliphist store" "${pkgs.mate.mate-polkit}/bin/polkit-mate" @@ -137,7 +141,7 @@ in { "minsize 1 1, title:^()$,class:^(steam)$" "stayfocused, title:^()$,class:^(steam)$" ]; - windowrule = [ "noanim,waybar" ]; + layerrule = [ "noanim,ags_bar_0" "noanim,selection" ]; general = { gaps_in = 1; gaps_out = pkgs.lib.mkDefault 5;