diff --git a/home/programs/ags/src/config.js b/home/programs/ags/config.js similarity index 69% rename from home/programs/ags/src/config.js rename to home/programs/ags/config.js index 246fbef..3759540 100644 --- a/home/programs/ags/src/config.js +++ b/home/programs/ags/config.js @@ -1,10 +1,8 @@ -// 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 { NotificationPopups } from "./notificationPopups.js"; const date = Variable("", { poll: [1000, 'date "+%H:%M %b %e."'], }); @@ -13,31 +11,6 @@ const date = Variable("", { // so to make a reuseable widget, make it a function // then you can simply instantiate one by calling it -//function Workspaces() { -// const activeId = hyprland.active.workspace.bind("id"); -// const workspaces = hyprland.bind("workspaces").as((ws) => -// ws.map(({ id }) => -// Widget.Button({ -// on_clicked: () => hyprland.messageAsync(`dispatch workspace ${id}`), -// child: Widget.Label(`${id}`), -// class_name: activeId.as((i) => `${i === id ? "focused" : ""}`), -// }), -// ), -// ); -// -// return Widget.Box({ -// class_name: "workspaces", -// children: workspaces, -// }); -//} - -function ClientTitle() { - return Widget.Label({ - class_name: "client-title", - label: "Label!", // hyprland.active.client.bind("title"), - }); -} - function Clock() { return Widget.Label({ class_name: "clock", @@ -45,24 +18,6 @@ function Clock() { }); } -// we don't need dunst or any other notification daemon -// because the Notifications module is a notification daemon itself -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 Media() { const label = Utils.watch("", mpris, "player-changed", () => { if (mpris.players[0]) { @@ -161,19 +116,13 @@ function SysTray() { // layout of the bar function Left() { - return Widget.Box({ - spacing: 8, - children: [ - // Workspaces(), - ClientTitle(), - ], - }); + return Widget.Box({}); } function Center() { return Widget.Box({ spacing: 8, - children: [Media(), Notification()], + children: [Media()], }); } @@ -202,13 +151,7 @@ function Bar(monitor = 0) { App.config({ style: "./style.css", - windows: [ - Bar(), - - // you can call it, for each monitor - // Bar(0), - // Bar(1) - ], + windows: [Bar(), NotificationPopups()], }); export {}; diff --git a/home/programs/ags/default.nix b/home/programs/ags/default.nix index bf4f2dd..3d65b1b 100644 --- a/home/programs/ags/default.nix +++ b/home/programs/ags/default.nix @@ -1,15 +1,23 @@ { inputs, pkgs, + config, ... -}: { +}: let + cfg = config.programs.ags; +in { imports = [inputs.ags.homeManagerModules.default]; + home.packages = with pkgs; [ + libdbusmenu-gtk3 + dart-sass + ]; + programs.ags = { enable = true; # null or path, leave as null if you don't want hm to manage the config - configDir = ./src; + configDir = ./.; # additional packages to add to gjs's runtime extraPackages = with pkgs; [ @@ -18,4 +26,17 @@ accountsservice ]; }; + systemd.user.services.ags = { + Unit = { + Description = "Aylur's Gtk Shell"; + PartOf = [ + "graphical-session.target" + ]; + }; + Service = { + ExecStart = "${cfg.package}/bin/ags"; + Restart = "on-failure"; + }; + Install.WantedBy = ["graphical-session.target"]; + }; } diff --git a/home/programs/ags/notificationPopups.js b/home/programs/ags/notificationPopups.js new file mode 100644 index 0000000..70c92b4 --- /dev/null +++ b/home/programs/ags/notificationPopups.js @@ -0,0 +1,124 @@ +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/programs/ags/src/style.css b/home/programs/ags/src/style.css deleted file mode 100644 index 9ac7355..0000000 --- a/home/programs/ags/src/style.css +++ /dev/null @@ -1,40 +0,0 @@ -window.bar { - background-color: @theme_bg_color; - color: @theme_fg_color; -} - -button { - min-width: 0; - padding-top: 0; - padding-bottom: 0; - background-color: transparent; -} - -button:active { - background-color: @theme_selected_bg_color; -} - -button:hover { - border-bottom: 3px solid @theme_fg_color; -} - -label { - font-weight: bold; -} - -.workspaces button.focused { - border-bottom: 3px solid @theme_selected_bg_color; -} - -.client-title { - color: @theme_selected_bg_color; -} - -.notification { - color: yellow; -} - -levelbar block, -highlight { - min-height: 10px; -} diff --git a/home/programs/ags/style.css b/home/programs/ags/style.css new file mode 100644 index 0000000..aeded67 --- /dev/null +++ b/home/programs/ags/style.css @@ -0,0 +1,57 @@ +window.notification-popups box.notifications { + padding: 0.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: 0.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 0.4em; + margin-top: 0.8em; +} + +.actions .action-button:first-child { + margin-left: 0; +} + +.actions .action-button:last-child { + margin-right: 0; +} diff --git a/home/programs/ags/src/tsconfig.json b/home/programs/ags/tsconfig.json similarity index 100% rename from home/programs/ags/src/tsconfig.json rename to home/programs/ags/tsconfig.json diff --git a/home/programs/ags/src/types b/home/programs/ags/types similarity index 100% rename from home/programs/ags/src/types rename to home/programs/ags/types diff --git a/home/programs/default.nix b/home/programs/default.nix index 870b8d9..eeecf72 100644 --- a/home/programs/default.nix +++ b/home/programs/default.nix @@ -18,6 +18,9 @@ proton-pass vesktop wl-clipboard + fractal cinny-desktop + gnome-control-center + playerctl ]; } diff --git a/home/programs/gtk.nix b/home/programs/gtk.nix index ac4ad27..6aed1a9 100644 --- a/home/programs/gtk.nix +++ b/home/programs/gtk.nix @@ -4,20 +4,16 @@ config, nixosConfig, ... -}: { +}: let + isDark = inputs.basix.schemeData.base16.${nixosConfig.style.scheme}.variant == "dark"; +in { home.pointerCursor = { inherit (nixosConfig.style.cursorTheme) name package size; gtk.enable = true; x11.enable = true; }; - gtk = let - isDark = inputs.basix.schemeData.base16.${nixosConfig.style.scheme}.variant == "dark"; - commonGtkConfig = { - gtk-decoration-layout = ":menu"; - gtk-application-prefer-dark-theme = isDark; - }; - in { + gtk = { enable = true; font = { @@ -26,8 +22,14 @@ size = 11; }; - gtk3.extraConfig = commonGtkConfig; - gtk4.extraConfig = commonGtkConfig; + gtk3.extraConfig = { + gtk-decoration-layout = ":menu"; + gtk-application-prefer-dark-theme = isDark; + }; + + gtk4.extraConfig = { + gtk-decoration-layout = ":menu"; + }; gtk2.configLocation = "${config.xdg.configHome}/gtk-2.0/gtkrc"; @@ -39,4 +41,12 @@ inherit (nixosConfig.style.gtk.theme) name package; }; }; + dconf.settings = { + "org/gnome/desktop/interface" = { + color-scheme = + if isDark + then "prefer-dark" + else "default"; + }; + }; } diff --git a/home/programs/niri/binds.nix b/home/programs/niri/binds.nix index 12ef432..4fef5e7 100644 --- a/home/programs/niri/binds.nix +++ b/home/programs/niri/binds.nix @@ -140,6 +140,9 @@ in { action.spawn = ["wpctl" "set-mute" "@DEFAULT_AUDIO_SOURCE@" "toggle"]; allow-when-locked = true; }; + "XF86AudioPlay" = { + action.spawn = ["playerctl" "play-pause"]; + }; "XF86MonBrightnessUp" = { action.spawn = ["brillo" "-q" "-u 300000" "-A 5"]; allow-when-locked = true; @@ -148,5 +151,9 @@ in { action.spawn = ["brillo" "-q" "-u 300000" "-U 5"]; allow-when-locked = true; }; + # set on media key since framework laptops have a gear as the button label + "XF86AudioMedia" = { + action.spawn = ["sh" "-c" "env XDG_CURRENT_DESKTOP=gnome gnome-control-center"]; + }; }; } diff --git a/home/programs/niri/default.nix b/home/programs/niri/default.nix index eb425e2..8f16689 100644 --- a/home/programs/niri/default.nix +++ b/home/programs/niri/default.nix @@ -8,6 +8,20 @@ colors = inputs.basix.schemeData.base16.${osConfig.style.scheme}.palette; in { imports = [./binds.nix]; + + xdg.portal = { + enable = true; + xdgOpenUsePortal = true; + extraPortals = [pkgs.xdg-desktop-portal-gtk pkgs.xdg-desktop-portal-gnome pkgs.gnome-keyring]; + config = { + common = { + default = ["gtk"]; + "org.freedesktop.impl.portal.ScreenCast" = ["gnome"]; + "org.freedesktop.impl.portal.Secret" = ["gnome-keyring"]; + }; + }; + }; + programs.niri = { settings = { input.keyboard.xkb = { diff --git a/hosts/default.nix b/hosts/default.nix index b463b90..4414e65 100644 --- a/hosts/default.nix +++ b/hosts/default.nix @@ -19,8 +19,9 @@ ++ [ ./vamos - ../system/services/regreet.nix + "${mod}/services/regreet.nix" "${mod}/programs/niri" + "${mod}/services/gnome.nix" self.nixosModules.style diff --git a/system/services/gnome.nix b/system/services/gnome.nix new file mode 100644 index 0000000..c5233bb --- /dev/null +++ b/system/services/gnome.nix @@ -0,0 +1,13 @@ +{pkgs, ...}: { + services = { + # needed for GNOME services outside of GNOME Desktop + dbus.packages = with pkgs; [ + gcr + gnome-settings-daemon + ]; + + gnome.gnome-keyring.enable = true; + + gvfs.enable = true; + }; +} diff --git a/system/services/regreet.nix b/system/services/regreet.nix index 41c990e..e70502a 100644 --- a/system/services/regreet.nix +++ b/system/services/regreet.nix @@ -16,10 +16,6 @@ in { programs.regreet = { enable = true; package = pkgs.greetd.regreet; - cageArgs = [ - "-s" - "-d" - ]; settings = { background = { path = mkDefault config.style.wallpaper; @@ -36,6 +32,17 @@ in { }; }; }; + + services.greetd = { + enable = true; + settings = rec { + default_session = { + command = "${lib.getExe pkgs.cage} -s -d -- ${lib.getExe config.programs.regreet.package}"; + user = "greeter"; + }; + initial_session = default_session; + }; + }; security.pam.services.greetd = { enableGnomeKeyring = true; fprintAuth = false;