repo: add ags, a gtk shell

commit 75cf6a4d67
Author: Anthony Rodriguez <anthony@nezia.dev>
Date:   Fri Oct 11 15:34:14 2024 +0200

    home/programs/niri: use gtk portals for most things, and gnome for screencast

commit 5ccb424079
Author: Anthony Rodriguez <anthony@nezia.dev>
Date:   Thu Oct 10 21:19:33 2024 +0200

    home/programs/niri: add play/pause, add settings button on fn key f12

commit 8b7164739d
Author: Anthony Rodriguez <anthony@nezia.dev>
Date:   Thu Oct 10 00:32:40 2024 +0200

    home/programs/niri: replace desktop-portal-gtk with gnome

commit fe40c6c72c
Author: Anthony Rodriguez <anthony@nezia.dev>
Date:   Thu Oct 10 00:21:31 2024 +0200

    home/programs/niri: add xdg-desktop-portal-gtk

commit 74b7df1245
Author: Anthony Rodriguez <anthony@nezia.dev>
Date:   Thu Oct 10 00:21:14 2024 +0200

    home/programs: add fractal

commit 3773095069
Author: Anthony Rodriguez <anthony@nezia.dev>
Date:   Thu Oct 10 00:21:05 2024 +0200

    home/programs/gtk: use color-scheme for gtk4.0

commit 8fef768f4f
Author: Anthony Rodriguez <anthony@nezia.dev>
Date:   Wed Oct 9 19:22:43 2024 +0200

    system/services/regreet: ensure that regreet starts dbus session

commit fcd88bfa8e
Author: Anthony Rodriguez <anthony@nezia.dev>
Date:   Wed Oct 9 19:22:20 2024 +0200

    repo: implement ags

    Implement ags, a wayland bar / GTK shell. Also added a few dependencies
    required for it to work properly.
This commit is contained in:
Anthony Rodriguez 2024-10-11 17:57:55 +02:00
parent 59b57de785
commit d0466f96da
Signed by: nezia
GPG key ID: EE3BE97C040A86CE
14 changed files with 278 additions and 118 deletions

View file

@ -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 {};

View file

@ -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"];
};
}

View file

@ -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))
}),
});
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -18,6 +18,9 @@
proton-pass
vesktop
wl-clipboard
fractal
cinny-desktop
gnome-control-center
playerctl
];
}

View file

@ -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";
};
};
}

View file

@ -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"];
};
};
}

View file

@ -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 = {

View file

@ -19,8 +19,9 @@
++ [
./vamos
../system/services/regreet.nix
"${mod}/services/regreet.nix"
"${mod}/programs/niri"
"${mod}/services/gnome.nix"
self.nixosModules.style

13
system/services/gnome.nix Normal file
View file

@ -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;
};
}

View file

@ -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;