diff --git a/flake.lock b/flake.lock
index 5dd08eb..b6ad012 100644
--- a/flake.lock
+++ b/flake.lock
@@ -1,5 +1,47 @@
{
"nodes": {
+ "ags": {
+ "inputs": {
+ "astal": "astal",
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1744557573,
+ "narHash": "sha256-XAyj0iDuI51BytJ1PwN53uLpzTDdznPDQFG4RwihlTQ=",
+ "owner": "aylur",
+ "repo": "ags",
+ "rev": "3ed9737bdbc8fc7a7c7ceef2165c9109f336bff6",
+ "type": "github"
+ },
+ "original": {
+ "owner": "aylur",
+ "repo": "ags",
+ "type": "github"
+ }
+ },
+ "astal": {
+ "inputs": {
+ "nixpkgs": [
+ "ags",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1742571008,
+ "narHash": "sha256-5WgfJAeBpxiKbTR/gJvxrGYfqQRge5aUDcGKmU1YZ1Q=",
+ "owner": "aylur",
+ "repo": "astal",
+ "rev": "dc0e5d37abe9424c53dcbd2506a4886ffee6296e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "aylur",
+ "repo": "astal",
+ "type": "github"
+ }
+ },
"home-manager": {
"inputs": {
"nixpkgs": [
@@ -38,6 +80,7 @@
},
"root": {
"inputs": {
+ "ags": "ags",
"home-manager": "home-manager",
"nixpkgs": "nixpkgs"
}
diff --git a/flake.nix b/flake.nix
index 911a9f8..6be263d 100644
--- a/flake.nix
+++ b/flake.nix
@@ -5,6 +5,9 @@
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
home-manager.url = "github:nix-community/home-manager";
home-manager.inputs.nixpkgs.follows = "nixpkgs"; # `follows` ensure it follows nixpkgs version
+
+ ags.url = "github:aylur/ags";
+ ags.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = {
diff --git a/modules/biscuit/xserver.nix b/modules/biscuit/xserver.nix
index fdcae9a..d6f0434 100644
--- a/modules/biscuit/xserver.nix
+++ b/modules/biscuit/xserver.nix
@@ -1,5 +1,6 @@
{...}: {
imports = [
../../pkgs/hyprland/biscuit.nix
+ ../../pkgs/ags/biscuit.nix
];
}
diff --git a/pkgs/ags/biscuit.nix b/pkgs/ags/biscuit.nix
new file mode 100644
index 0000000..001a731
--- /dev/null
+++ b/pkgs/ags/biscuit.nix
@@ -0,0 +1,11 @@
+{inputs, pkgs, system, ...}: {
+ imports = [ inputs.ags.homeManagerModules.default ];
+ program.ags = {
+ enable = true;
+ configDir = ./biscuit;
+ extraPackages = with pkgs; [
+ inputs.ags.packages.${pkgs.system}.battery
+ fzf
+ ];
+ };
+}
diff --git a/pkgs/ags/biscuit/.gitignore b/pkgs/ags/biscuit/.gitignore
new file mode 100644
index 0000000..298eb4d
--- /dev/null
+++ b/pkgs/ags/biscuit/.gitignore
@@ -0,0 +1,2 @@
+node_modules/
+@girs/
diff --git a/pkgs/ags/biscuit/app.ts b/pkgs/ags/biscuit/app.ts
new file mode 100644
index 0000000..4b7ea48
--- /dev/null
+++ b/pkgs/ags/biscuit/app.ts
@@ -0,0 +1,13 @@
+import { App } from "astal/gtk3"
+import style from "./style.scss"
+import Bar from "./widget/Bar"
+
+App.start({
+ css: style,
+ instanceName: "js",
+ requestHandler(request, res) {
+ print(request)
+ res("ok")
+ },
+ main: () => App.get_monitors().map(Bar),
+})
diff --git a/pkgs/ags/biscuit/env.d.ts b/pkgs/ags/biscuit/env.d.ts
new file mode 100644
index 0000000..467c0a4
--- /dev/null
+++ b/pkgs/ags/biscuit/env.d.ts
@@ -0,0 +1,21 @@
+declare const SRC: string
+
+declare module "inline:*" {
+ const content: string
+ export default content
+}
+
+declare module "*.scss" {
+ const content: string
+ export default content
+}
+
+declare module "*.blp" {
+ const content: string
+ export default content
+}
+
+declare module "*.css" {
+ const content: string
+ export default content
+}
diff --git a/pkgs/ags/biscuit/package.json b/pkgs/ags/biscuit/package.json
new file mode 100644
index 0000000..23b6342
--- /dev/null
+++ b/pkgs/ags/biscuit/package.json
@@ -0,0 +1,6 @@
+{
+ "name": "astal-shell",
+ "dependencies": {
+ "astal": "/home/biscuit/.local/share/ags"
+ }
+}
diff --git a/pkgs/ags/biscuit/style.scss b/pkgs/ags/biscuit/style.scss
new file mode 100644
index 0000000..5c20382
--- /dev/null
+++ b/pkgs/ags/biscuit/style.scss
@@ -0,0 +1,107 @@
+@use "sass:color";
+
+$bg: #212223;
+$fg: #f1f1f1;
+$accent: #378DF7;
+$radius: 7px;
+
+window.Bar {
+ border: none;
+ box-shadow: none;
+ background-color: $bg;
+ color: $fg;
+ font-size: 1.1em;
+ font-weight: bold;
+
+ label {
+ margin: 0 8px;
+ }
+
+ .Workspaces {
+ button {
+ all: unset;
+ background-color: transparent;
+
+ &:hover label {
+ background-color: color.adjust($fg, $alpha: -0.84);
+ border-color: color.adjust($accent, $alpha: -0.8);
+ }
+
+ &:active label {
+ background-color: color.adjust($fg, $alpha: -0.8)
+ }
+ }
+
+ label {
+ transition: 200ms;
+ padding: 0 8px;
+ margin: 2px;
+ border-radius: $radius;
+ border: 1pt solid transparent;
+ }
+
+ .focused label {
+ color: $accent;
+ border-color: $accent;
+ }
+ }
+
+ .SysTray {
+ margin-right: 8px;
+
+ button {
+ padding: 0 4px;
+ }
+ }
+
+ .FocusedClient {
+ color: $accent;
+ }
+
+ .Media .Cover {
+ min-height: 1.2em;
+ min-width: 1.2em;
+ border-radius: $radius;
+ background-position: center;
+ background-size: contain;
+ }
+
+ .Battery label {
+ padding-left: 0;
+ margin-left: 0;
+ }
+
+ .AudioSlider {
+ * {
+ all: unset;
+ }
+
+ icon {
+ margin-right: .6em;
+ }
+
+ & {
+ margin: 0 1em;
+ }
+
+ trough {
+ background-color: color.adjust($fg, $alpha: -0.8);
+ border-radius: $radius;
+ }
+
+ highlight {
+ background-color: $accent;
+ min-height: .8em;
+ border-radius: $radius;
+ }
+
+ slider {
+ background-color: $fg;
+ border-radius: $radius;
+ min-height: 1em;
+ min-width: 1em;
+ margin: -.2em;
+ }
+ }
+}
+
diff --git a/pkgs/ags/biscuit/tsconfig.json b/pkgs/ags/biscuit/tsconfig.json
new file mode 100644
index 0000000..9471e35
--- /dev/null
+++ b/pkgs/ags/biscuit/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "$schema": "https://json.schemastore.org/tsconfig",
+ "compilerOptions": {
+ "experimentalDecorators": true,
+ "strict": true,
+ "target": "ES2022",
+ "module": "ES2022",
+ "moduleResolution": "Bundler",
+ // "checkJs": true,
+ // "allowJs": true,
+ "jsx": "react-jsx",
+ "jsxImportSource": "astal/gtk3",
+ }
+}
diff --git a/pkgs/ags/biscuit/widget/Bar.tsx b/pkgs/ags/biscuit/widget/Bar.tsx
new file mode 100644
index 0000000..b1fb229
--- /dev/null
+++ b/pkgs/ags/biscuit/widget/Bar.tsx
@@ -0,0 +1,173 @@
+import { App } from "astal/gtk3"
+import { Variable, GLib, bind } from "astal"
+import { Astal, Gtk, Gdk } from "astal/gtk3"
+import Hyprland from "gi://AstalHyprland"
+import Mpris from "gi://AstalMpris"
+import Battery from "gi://AstalBattery"
+import Wp from "gi://AstalWp"
+import Network from "gi://AstalNetwork"
+import Tray from "gi://AstalTray"
+
+function SysTray() {
+ const tray = Tray.get_default()
+
+ return
+ {bind(tray, "items").as(items => items.map(item => (
+ ["dbusmenu", ag])}
+ menuModel={bind(item, "menuModel")}>
+
+
+ )))}
+
+}
+
+function Wifi() {
+ const network = Network.get_default()
+ const wifi = bind(network, "wifi")
+
+ return
+ {wifi.as(wifi => wifi && (
+
+ ))}
+
+
+}
+
+function AudioSlider() {
+ const speaker = Wp.get_default()?.audio.defaultSpeaker!
+
+ return
+
+ speaker.volume = value}
+ value={bind(speaker, "volume")}
+ />
+
+}
+
+function BatteryLevel() {
+ const bat = Battery.get_default()
+
+ return
+
+
+}
+
+function Media() {
+ const mpris = Mpris.get_default()
+
+ return
+ {bind(mpris, "players").as(ps => ps[0] ? (
+
+
+ `background-image: url('${cover}');`
+ )}
+ />
+
+ ) : (
+
+ ))}
+
+}
+
+
+function Workspaces() {
+ const hypr = Hyprland.get_default();
+
+ return (
+
+ {bind(hypr, "focusedWorkspace").as((fw) => {
+ if (!fw) return null;
+
+ // Determine the current chunk of 5 visible workspace buttons
+ const currentChunkStart = Math.floor((fw.id - 1) / 5) * 5 + 1;
+ const visibleIds = Array.from({ length: 5 }, (_, i) => currentChunkStart + i);
+
+ return visibleIds.map((id) => {
+ // Try to get the real workspace, fall back to a dummy one if it doesn't exist
+ const ws =
+ hypr.workspaces.find((w) => w.id === id) ??
+ Hyprland.Workspace.dummy(id, null);
+
+ return (
+
+ );
+ });
+ })}
+
+ );
+}
+
+function FocusedClient() {
+ const hypr = Hyprland.get_default()
+ const focused = bind(hypr, "focusedClient")
+
+ return
+ {focused.as(client => (
+ client &&
+ ))}
+
+}
+
+function Time({ format = "%H:%M %a %b %e" }) {
+ const time = Variable("").poll(1000, () =>
+ GLib.DateTime.new_now_local().format(format)!)
+
+ return