diff --git a/astal/.gitignore b/astal/.gitignore
new file mode 100644
index 0000000..298eb4d
--- /dev/null
+++ b/astal/.gitignore
@@ -0,0 +1,2 @@
+node_modules/
+@girs/
diff --git a/astal/app.ts b/astal/app.ts
new file mode 100644
index 0000000..08a2f70
--- /dev/null
+++ b/astal/app.ts
@@ -0,0 +1,10 @@
+import { App } from "astal/gtk3";
+import style from "./style.scss";
+import Bar from "./widget/Bar";
+
+App.start({
+ css: style,
+ main() {
+ App.get_monitors().map(Bar);
+ },
+});
diff --git a/astal/env.d.ts b/astal/env.d.ts
new file mode 100644
index 0000000..467c0a4
--- /dev/null
+++ b/astal/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/astal/flake.lock b/astal/flake.lock
new file mode 100644
index 0000000..562d628
--- /dev/null
+++ b/astal/flake.lock
@@ -0,0 +1,70 @@
+{
+ "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"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1750365781,
+ "narHash": "sha256-XE/lFNhz5lsriMm/yjXkvSZz5DfvKJLUjsS6pP8EC50=",
+ "owner": "nixos",
+ "repo": "nixpkgs",
+ "rev": "08f22084e6085d19bcfb4be30d1ca76ecb96fe54",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nixos",
+ "ref": "nixos-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "ags": "ags",
+ "nixpkgs": "nixpkgs"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/astal/flake.nix b/astal/flake.nix
new file mode 100644
index 0000000..7554ded
--- /dev/null
+++ b/astal/flake.nix
@@ -0,0 +1,62 @@
+{
+ description = "Desktop widgets";
+
+ inputs = {
+ nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
+
+ ags = {
+ url = "github:aylur/ags";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
+ };
+
+ outputs =
+ {
+ self,
+ nixpkgs,
+ ags,
+ }:
+ let
+ system = "x86_64-linux";
+ pkgs = nixpkgs.legacyPackages.${system};
+
+ extraPkgs = with ags.packages.${system}; [
+ hyprland
+ battery
+ wireplumber
+ network
+ ];
+ in
+ {
+ packages.${system} = {
+ status-bar = ags.lib.bundle {
+ inherit pkgs;
+ src = ./.;
+ name = "status-bar";
+ entry = "app.ts";
+
+ extraPackages =
+ with pkgs;
+ extraPkgs
+ ++ [
+ libgtop
+ ];
+ };
+ };
+
+ devShells.${system} = {
+ default = pkgs.mkShell {
+ buildInputs = [
+ (ags.packages.${system}.default.override {
+ extraPackages =
+ with pkgs;
+ extraPkgs
+ ++ [
+ libgtop
+ ];
+ })
+ ];
+ };
+ };
+ };
+}
diff --git a/astal/package-lock.json b/astal/package-lock.json
new file mode 100644
index 0000000..635a9c5
--- /dev/null
+++ b/astal/package-lock.json
@@ -0,0 +1,21 @@
+{
+ "name": "astal-shell",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "astal-shell",
+ "dependencies": {
+ "astal": "/nix/store/pvb3x021mr6xknm91gqq76gy32n96vj0-astal-gjs/share/astal/gjs"
+ }
+ },
+ "../../../../nix/store/pvb3x021mr6xknm91gqq76gy32n96vj0-astal-gjs/share/astal/gjs": {
+ "name": "astal",
+ "license": "LGPL-2.1"
+ },
+ "node_modules/astal": {
+ "resolved": "../../../../nix/store/pvb3x021mr6xknm91gqq76gy32n96vj0-astal-gjs/share/astal/gjs",
+ "link": true
+ }
+ }
+}
diff --git a/astal/package.json b/astal/package.json
new file mode 100644
index 0000000..32f5d43
--- /dev/null
+++ b/astal/package.json
@@ -0,0 +1,6 @@
+{
+ "name": "astal-shell",
+ "dependencies": {
+ "astal": "/nix/store/pvb3x021mr6xknm91gqq76gy32n96vj0-astal-gjs/share/astal/gjs"
+ }
+}
diff --git a/astal/style.scss b/astal/style.scss
new file mode 100644
index 0000000..a1ad609
--- /dev/null
+++ b/astal/style.scss
@@ -0,0 +1,122 @@
+@use "sass:color";
+
+$bg: #212223;
+$fg: #f1f1f1;
+$accent: #378DF7;
+$accent-white: #ffffff;
+$radius: 7px;
+$bg-color-6: rgb(40, 42, 54);
+$inactive-bg-color: rgb(68, 71, 90);
+
+window.Bar {
+ border: none;
+ box-shadow: none;
+ font-family: Dejavu Sans Mono;
+ background-color: color.adjust($bg, $alpha: -0.2);
+ color: $fg;
+ font-size: 1.1em;
+
+ label {
+ margin: 0 8px;
+ }
+
+ .status-box {
+ background-color: $bg-color-6;
+ padding: 0 4px;
+ margin: 0 4px;
+ font-size: 16px;
+ }
+
+ .inactive {
+ background-color: $inactive-bg-color;
+ }
+
+ .workspaces {
+ font-weight: bold;
+
+ 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: 3px 8px;
+ margin: 0px;
+ border-radius: 0;
+ border: 3px solid transparent;
+ }
+
+ .focused label {
+ background-color: color.adjust($accent-white, $alpha: -0.84);
+ border-bottom: 3px solid $accent-white;
+ }
+ }
+
+ .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/astal/tsconfig.json b/astal/tsconfig.json
new file mode 100644
index 0000000..9471e35
--- /dev/null
+++ b/astal/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/astal/widget/Bar.tsx b/astal/widget/Bar.tsx
new file mode 100644
index 0000000..4b26138
--- /dev/null
+++ b/astal/widget/Bar.tsx
@@ -0,0 +1,156 @@
+import { App, Astal, Gtk, Gdk } from "astal/gtk3"
+import { bind, derive, GLib, Variable } from "astal"
+import Hyprland from "gi://AstalHyprland"
+import Wp from "gi://AstalWp"
+import Network from "gi://AstalNetwork"
+import GTop from "gi://GTop"
+import { calc_cpu_usage, get_cpu_snapshot, get_disk_space, get_ram_info } from "./cpu"
+import AstalBattery from "gi://AstalBattery"
+
+const time = Variable("").poll(1000, "date")
+
+function Workspaces() {
+ const hypr = Hyprland.get_default()
+
+ return
+ {bind(hypr, "workspaces").as(wss => wss.filter(ws => !(ws.id >= -99 && ws.id <= -2))
+ .sort((a, b) => a.id - b.id)
+ .map(ws => (
+ //@ts-ignore
+
+ )))}
+
+}
+
+function Audio() {
+ const speaker = Wp.get_default()?.default_speaker!
+ const derived = Variable.derive([bind(speaker, "volume"), bind(speaker, "mute")], (volume: number, muted: boolean) => {
+ if (muted) {
+ return { label: ` (muted)`, muted }
+ }
+ return { label: `${Math.floor(volume * 100)}% ` }
+ })
+
+ return ["status-box", v.muted && "inactive"].filter(Boolean).join(" "))}>
+
+}
+
+function NetworkModule() {
+ const network = Network.get_default()
+ const wifi = network.wifi;
+ const wired = network.wired
+
+ const derived = Variable.derive([bind(network, "primary"), bind(wifi, "ssid"), bind(wifi, "strength")], (primary, ssid, strength) => {
+ if (primary === Network.Primary.WIRED) {
+ return { label: "🖧 Wired" }
+ }
+ if (wifi.active_access_point !== null) {
+ return { label: `${ssid} (${strength}%) ` }
+ }
+ return { label: `Disconnected ⚠` }
+ })
+
+ return (
+
+
+ )
+}
+
+let s1 = get_cpu_snapshot();
+let cpu_usage_percent = Variable(0);
+GLib.timeout_add(GLib.PRIORITY_DEFAULT, 1000, () => {
+ const s2 = get_cpu_snapshot();
+ cpu_usage_percent.set(calc_cpu_usage(s1, s2));
+ s1 = s2;
+
+ return true;
+});
+function Cpu() {
+ return
+
+}
+
+let info = Variable(get_ram_info())
+GLib.timeout_add(GLib.PRIORITY_DEFAULT, 1000, () => {
+ info.set(get_ram_info())
+
+ return true
+})
+function Ram() {
+ return
+
+}
+
+let disk_space = Variable(get_disk_space())
+function Disk() {
+ return
+
+}
+
+function Battery() {
+ const battery = AstalBattery.get_default()
+ const battery_info = Variable.derive([bind(battery, "percentage"), bind(battery, "charging")], (percentage, charging) => {
+ const full_percentage = Math.floor(percentage * 100)
+ if (charging) {
+ return { label: `${full_percentage == 100 ? "FULL" : "CHR"}: ${full_percentage}%` }
+ }
+ return { label: `${full_percentage == 100 ? "FULL" : "BAT"}: ${full_percentage}` }
+ })
+
+ return
+
+}
+
+let current_time = Variable(GLib.DateTime.new_now_local().format("%H:%M"))
+GLib.timeout_add(GLib.PRIORITY_DEFAULT, 1000, () => {
+ const now = GLib.DateTime.new_now_local()
+ current_time.set(now.format("%H:%M"))
+ return true
+})
+function Time() {
+
+ return
+
+
+}
+
+export default function Bar(gdkmonitor: Gdk.Monitor) {
+ const { BOTTOM, LEFT, RIGHT } = Astal.WindowAnchor
+
+ //@ts-ignore
+ return
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+}
diff --git a/astal/widget/cpu.ts b/astal/widget/cpu.ts
new file mode 100644
index 0000000..0175b30
--- /dev/null
+++ b/astal/widget/cpu.ts
@@ -0,0 +1,46 @@
+import { GLib, Variable } from "astal";
+import GTop from "gi://GTop";
+
+type Snapshot = {
+ total: number;
+ user: number;
+ sys: number;
+ idle: number;
+};
+
+export function get_cpu_snapshot() {
+ const cpu = new GTop.glibtop_cpu();
+ GTop.glibtop_get_cpu(cpu);
+ return {
+ total: cpu.total,
+ user: cpu.user + cpu.nice,
+ sys: cpu.sys,
+ idle: cpu.idle,
+ };
+}
+
+export function calc_cpu_usage(a: Snapshot, b: Snapshot) {
+ const total_diff = b.total - a.total;
+ const active_diff = b.user + b.sys - (a.user + a.sys);
+ return Math.round(total_diff > 0 ? (100 * active_diff) / total_diff : 0);
+}
+
+export function get_ram_info() {
+ const mem = new GTop.glibtop_mem();
+ GTop.glibtop_get_mem(mem);
+ return {
+ total: mem.total,
+ used: mem.total - mem.free - mem.cached - mem.buffer,
+ free: mem.free,
+ };
+}
+
+export function get_disk_space() {
+ const usage = new GTop.glibtop_fsusage();
+ GTop.glibtop_get_fsusage(usage, "/");
+
+ const free_bytes = usage.bavail * usage.block_size;
+ const free_gib = free_bytes / 1024 ** 3;
+
+ return free_gib.toFixed(2);
+}
diff --git a/nix/flake.lock b/nix/flake.lock
index 638e9be..624877f 100644
--- a/nix/flake.lock
+++ b/nix/flake.lock
@@ -1,5 +1,49 @@
{
"nodes": {
+ "ags": {
+ "inputs": {
+ "astal": "astal",
+ "nixpkgs": [
+ "status-bar",
+ "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": [
+ "status-bar",
+ "ags",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1742571008,
+ "narHash": "sha256-5WgfJAeBpxiKbTR/gJvxrGYfqQRge5aUDcGKmU1YZ1Q=",
+ "owner": "aylur",
+ "repo": "astal",
+ "rev": "dc0e5d37abe9424c53dcbd2506a4886ffee6296e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "aylur",
+ "repo": "astal",
+ "type": "github"
+ }
+ },
"custom-fonts": {
"inputs": {
"nixpkgs": [
@@ -39,11 +83,11 @@
]
},
"locked": {
- "lastModified": 1750304462,
- "narHash": "sha256-Mj5t4yX05/rXnRqJkpoLZTWqgStB88Mr/fegTRqyiWc=",
+ "lastModified": 1750650158,
+ "narHash": "sha256-cGdpDr/OUjS5rfl0lH7CjnIFWtpOFQOcRkvBu9CV+Cs=",
"owner": "nix-community",
"repo": "home-manager",
- "rev": "863842639722dd12ae9e37ca83bcb61a63b36f6c",
+ "rev": "3bd646138a9c71f244a9293dde6f59ae1c6875ab",
"type": "github"
},
"original": {
@@ -110,11 +154,11 @@
},
"nixpkgs_2": {
"locked": {
- "lastModified": 1750134718,
- "narHash": "sha256-v263g4GbxXv87hMXMCpjkIxd/viIF7p3JpJrwgKdNiI=",
+ "lastModified": 1750506804,
+ "narHash": "sha256-VLFNc4egNjovYVxDGyBYTrvVCgDYgENp5bVi9fPTDYc=",
"owner": "nixos",
"repo": "nixpkgs",
- "rev": "9e83b64f727c88a7711a2c463a7b16eedb69a84c",
+ "rev": "4206c4cb56751df534751b058295ea61357bbbaa",
"type": "github"
},
"original": {
@@ -172,8 +216,26 @@
"nixos-wsl": "nixos-wsl",
"nixpkgs": "nixpkgs_2",
"pesde": "pesde",
- "rokit": "rokit"
+ "rokit": "rokit",
+ "status-bar": "status-bar"
}
+ },
+ "status-bar": {
+ "inputs": {
+ "ags": "ags",
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "path": "../astal",
+ "type": "path"
+ },
+ "original": {
+ "path": "../astal",
+ "type": "path"
+ },
+ "parent": []
}
},
"root": "root",
diff --git a/nix/flake.nix b/nix/flake.nix
index 158cf61..05b1b6f 100644
--- a/nix/flake.nix
+++ b/nix/flake.nix
@@ -14,6 +14,10 @@
rokit.inputs.nixpkgs.follows = "nixpkgs";
custom-fonts.url = "path:./fonts";
custom-fonts.inputs.nixpkgs.follows = "nixpkgs";
+ status-bar = {
+ url = "path:../astal";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
};
outputs =
@@ -72,6 +76,7 @@
inputs.pesde.packages.${host.architecture}.default
inputs.mantle.packages.${host.architecture}.default
inputs.rokit.packages.${host.architecture}.default
+ inputs.status-bar.packages.${host.architecture}.status-bar
];
fonts.packages = [
inputs.custom-fonts.packages.${host.architecture}.default
diff --git a/nix/modules/desktop.nix b/nix/modules/desktop.nix
index 816ff2e..3764651 100644
--- a/nix/modules/desktop.nix
+++ b/nix/modules/desktop.nix
@@ -40,6 +40,7 @@
services.tumbler.enable = true;
services.displayManager.ly.enable = true;
rofi.enable = true;
+ services.upower.enable = true;
home-manager.users.luca = {
programs = {
@@ -139,7 +140,7 @@
];
exec-once = [
"swaybg -i ~/.config/wallpaper/bg.jpg"
- "waybar"
+ "status-bar"
];
monitor = [
"eDP-1, 1920x1080, 0x0, 1"