From 11c257e4b3c394b49d81069badf04cd8e7df6142 Mon Sep 17 00:00:00 2001 From: rocketcamel Date: Fri, 5 Dec 2025 20:32:18 -0800 Subject: [PATCH] feat!: migrate to ags v3, redesign styles --- .gitignore | 2 ++ flake.nix | 1 - style.scss | 59 ++++++++++++++++++++++++++++++++++++++----- widget/Bar.tsx | 32 ++++++++++++++++------- widget/audio.tsx | 25 ++++++++++-------- widget/battery.tsx | 25 ++++++++++-------- widget/cpu-widget.tsx | 27 ++++++++++---------- widget/cpu.ts | 52 ++++++++++++++++++-------------------- widget/disk.tsx | 13 ++++++---- widget/network.tsx | 33 ++++++++---------------- widget/ram.tsx | 19 ++++++-------- widget/time.tsx | 22 +++++++--------- widget/title.tsx | 6 ----- widget/workspaces.tsx | 19 ++------------ 14 files changed, 182 insertions(+), 153 deletions(-) diff --git a/.gitignore b/.gitignore index 298eb4d..b7f3dfd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ node_modules/ @girs/ + +result diff --git a/flake.nix b/flake.nix index 6ff83d0..1ad14e4 100644 --- a/flake.nix +++ b/flake.nix @@ -77,7 +77,6 @@ mkdir -p $out/bin mkdir -p $out/share cp -r * $out/share - jj ags bundle app.ts $out/bin/${pname} -d "SRC='$out/share'" ''; }; diff --git a/style.scss b/style.scss index 522b9db..aaa90f8 100644 --- a/style.scss +++ b/style.scss @@ -1,7 +1,7 @@ @use "sass:color"; -$bg: #212223; -$fg: #f1f1f1; +$bg: #1a1b26; +$fg: #a9b1d6; $accent: #378DF7; $accent-white: #ffffff; $radius: 7px; @@ -12,7 +12,8 @@ window.Bar { border: none; box-shadow: none; font-family: Dejavu Sans Mono; - background-color: color.adjust($bg, $alpha: -0.2); + // background-color: color.adjust($bg, $alpha: -0.2); + background-color: $bg; color: $fg; font-size: 1.1em; @@ -21,12 +22,58 @@ window.Bar { } .status-box { - background-color: $bg-color-6; - padding: 0 4px; - margin: 0 2px; + // background-color: $bg-color-6; + // padding: 0 4px; + margin: 0 4px; font-size: 16px; } + .disk { + color: #e0af68; + border-bottom: 4px solid #e0af68; + } + + .mem { + color: #bb9af7; + border-bottom: 4px solid #bb9af7; + } + + .cpu { + color: #9ece6a; + border-bottom: 4px solid #9ece6a; + } + + .net { + color: #7aa2f7; + border-bottom: 4px solid #7aa2f7; + } + + .audio { + color: #2ac3de; + border-bottom: 4px solid #2ac3de; + } + + .date-popover { + all: unset; + + color: #73daca; + border-bottom: 4px solid #73daca; + + &>* { + all: unset; + border: none; + box-shadow: none; + background: transparent; + } + } + + .separator { + background-color: #444b6a; + min-width: 2px; + min-height: 1px; + margin: 8px 0; + } + .inactive { background-color: $inactive-bg-color; } diff --git a/widget/Bar.tsx b/widget/Bar.tsx index 341dfe8..3e6f289 100644 --- a/widget/Bar.tsx +++ b/widget/Bar.tsx @@ -1,15 +1,14 @@ -import { Astal, Gtk, Gdk } from "ags/gtk4"; -// import Workspaces from "./workspaces"; -// import Audio from "./audio"; -// import NetworkModule from "./network"; -// import Cpu from "./cpu-widget"; -// import Ram from "./ram"; -// import Disk from "./disk"; -// import Battery from "./battery"; +import { Astal, Gdk, Gtk } from "ags/gtk4"; import Time from "./time"; import Title from "./title"; import app from "ags/gtk4/app"; import Workspaces from "./workspaces"; +import Battery from "./battery"; +import Disk from "./disk"; +import NetworkModule from "./network"; +import Ram from "./ram"; +import Cpu from "./cpu-widget"; +import Audio from "./audio"; export default function Bar(gdkmonitor: Gdk.Monitor) { const { TOP, LEFT, RIGHT } = Astal.WindowAnchor; @@ -25,11 +24,26 @@ export default function Bar(gdkmonitor: Gdk.Monitor) { application={app} > - + + + + </box> <box $type="end"> + <Gtk.Separator class="separator" /> + <Audio /> + <Gtk.Separator class="separator" /> + <NetworkModule /> + <Gtk.Separator class="separator" /> + <Cpu /> + <Gtk.Separator class="separator" /> + <Ram /> + <Gtk.Separator class="separator" /> + <Disk /> + <Gtk.Separator class="separator" /> + <Battery /> <Time /> </box> </centerbox> diff --git a/widget/audio.tsx b/widget/audio.tsx index 72a4fd7..e197e4f 100644 --- a/widget/audio.tsx +++ b/widget/audio.tsx @@ -1,18 +1,21 @@ -import { bind, Variable } from "astal" import AstalWp from "gi://AstalWp?version=0.1" +import { createBinding, createMemo } from "gnim" export default function Audio() { const speaker = AstalWp.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)}% ` } - // }) + const display = createMemo(() => { + const volume = createBinding(speaker, "volume") + const muted = createBinding(speaker, "mute") - // return <box className={derived((v) => ["status-box", v.muted && "inactive"].filter(Boolean).join(" "))}> - // <label - // label={derived((v) => v.label)} /> - // </box> + if (muted()) { + return "(muted)" + } + return `${Math.floor(volume() * 100)}% ` + }) + return ( + <box class="status-box audio"> + <label label={display} /> + </box> + ) } diff --git a/widget/battery.tsx b/widget/battery.tsx index ed08c49..7a30aaf 100644 --- a/widget/battery.tsx +++ b/widget/battery.tsx @@ -1,21 +1,24 @@ -import { bind, Variable } from "astal" +import { Gtk } from "ags/gtk4" import AstalBattery from "gi://AstalBattery?version=0.1" +import { createBinding, createComputed } from "gnim" export default 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}%` } - }) + const percentage = createBinding(battery, "percentage") + const full_percentage = Math.floor(percentage.peek() * 100) + const battery_info = createComputed(() => (`${full_percentage === 100 ? "FULL" : "CHR"}: ${full_percentage}%`)) if (!battery.is_battery) { return <></> } - return <box className="status-box"> - <label label={battery_info((i) => i.label)} /> - </box> + + return ( + <> + <box class="status-box"> + <label label={battery_info} /> + </box> + <Gtk.Separator /> + </> + ) } diff --git a/widget/cpu-widget.tsx b/widget/cpu-widget.tsx index 3d8af80..f63f5f4 100644 --- a/widget/cpu-widget.tsx +++ b/widget/cpu-widget.tsx @@ -1,19 +1,18 @@ -import { bind, GLib, Variable } from "astal"; +import { createPoll } from "ags/time"; import { calc_cpu_usage, get_cpu_snapshot } from "./cpu"; -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; -}); - +let s1 = get_cpu_snapshot() export default function Cpu() { - return <box className="status-box"> - <label label={bind(cpu_usage_percent).as((u) => `${u}% `)} /> - </box> + const usage = createPoll(0, 1000, () => { + const s2 = get_cpu_snapshot() + let calc = calc_cpu_usage(s1, s2) + s1 = s2 + return calc + }) + return ( + <box class="status-box cpu"> + <label label={usage.as(u => `CPU: ${u}% `)} /> + </box> + ) } diff --git a/widget/cpu.ts b/widget/cpu.ts index 3863c3a..5ad4429 100644 --- a/widget/cpu.ts +++ b/widget/cpu.ts @@ -1,58 +1,56 @@ -import { GLib, Variable } from "astal"; -import GTop from "gi://GTop"; -import Wp05 from "gi://Wp"; +import GTop from "gi://GTop" type Snapshot = { - total: number; - user: number; - sys: number; - idle: number; -}; + total: number + user: number + sys: number + idle: number +} export function get_cpu_snapshot() { - const cpu = new GTop.glibtop_cpu(); - GTop.glibtop_get_cpu(cpu); + 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); + 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); + 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, - }; + } } function format_bytes(bytes: number) { - let units = ["B", "KiB", "MiB", "GiB", "TiB"]; - let i = 0; - let num: number = bytes; + let units = ["B", "KiB", "MiB", "GiB", "TiB"] + let i = 0 + let num: number = bytes while (num >= 1024 && i < units.length - 1) { - num /= 1024; - i++; + num /= 1024 + i++ } - return `${num.toFixed(2)}${units[i]}`; + return `${num.toFixed(2)}${units[i]}` } export function get_disk_space() { - const usage = new GTop.glibtop_fsusage(); - GTop.glibtop_get_fsusage(usage, "/"); + const usage = new GTop.glibtop_fsusage() + GTop.glibtop_get_fsusage(usage, "/") - const free_bytes = usage.bavail * usage.block_size; + const free_bytes = usage.bavail * usage.block_size - return format_bytes(free_bytes); + return format_bytes(free_bytes) } diff --git a/widget/disk.tsx b/widget/disk.tsx index 590fa44..dc4ebb7 100644 --- a/widget/disk.tsx +++ b/widget/disk.tsx @@ -1,10 +1,13 @@ -import { bind, Variable } from "astal" import { get_disk_space } from "./cpu" +import { createPoll } from "ags/time" -let disk_space = Variable(get_disk_space()).poll(5000, () => get_disk_space()) export default function Disk() { - return <box className="status-box"> - <label label={bind(disk_space).as((s) => `${s}`)} /> - </box> + const disk = createPoll(get_disk_space(), 5000, () => get_disk_space()) + + return ( + <box class="status-box disk"> + <label label={disk.as(d => `Disk: ${d}`)} /> + </box> + ) } diff --git a/widget/network.tsx b/widget/network.tsx index f5c8f0b..4bf5748 100644 --- a/widget/network.tsx +++ b/widget/network.tsx @@ -1,7 +1,7 @@ -import { bind, Variable } from "astal"; import AstalNetwork from "gi://AstalNetwork?version=0.1"; +import { createBinding, createMemo } from "gnim"; -const disconnected_label = { label: `Disconnected ⚠` }; +const disconnected_label = `Disconnected ⚠` function create_label( primary: AstalNetwork.Primary, @@ -9,39 +9,28 @@ function create_label( wifi?: AstalNetwork.Wifi, ) { if (primary === AstalNetwork.Primary.WIRED) { - return { label: `🖧 Wired ${wired.device.interface}` }; + return `🖧 Wired ${wired.device.interface}` } if (!wifi) { return disconnected_label; } if (wifi.active_access_point !== null) { - return { label: `${wifi.ssid} (${wifi.strength}%) ` }; + return `${wifi.ssid} (${wifi.strength}%) ` } return disconnected_label; } export default function NetworkModule() { const network = AstalNetwork.get_default(); - const wifi = network.wifi; - const wired = network.wired; + const primary = createBinding(network, "primary") + const wifi = createBinding(network, "wifi") + const wired = createBinding(network, "wired") - let derived; - if (!wifi) { - derived = Variable.derive([bind(network, "primary")], (primary) => { - return create_label(primary, wired); - }); - } else { - derived = Variable.derive( - [bind(network, "primary"), bind(wifi, "ssid"), bind(wifi, "strength")], - (primary) => { - return create_label(primary, wired, wifi); - }, - ); - } + const display = createMemo(() => create_label(primary(), wired(), wifi())) return ( - <box className="status-box"> - <label label={derived((v) => v.label)} /> + <box class="status-box net"> + <label label={display} /> </box> - ); + ) } diff --git a/widget/ram.tsx b/widget/ram.tsx index 8abea28..48e3ea7 100644 --- a/widget/ram.tsx +++ b/widget/ram.tsx @@ -1,16 +1,13 @@ -import { bind, GLib, Variable } from "astal" import { get_ram_info } from "./cpu" - -let info = Variable(get_ram_info()) -GLib.timeout_add(GLib.PRIORITY_DEFAULT, 1000, () => { - info.set(get_ram_info()) - - return true -}) +import { createPoll } from "ags/time" export default function Ram() { - return <box className="status-box"> - <label label={bind(info).as((i) => `${Math.round((i.used / i.total) * 100)}% `)} /> - </box> + const info = createPoll(get_ram_info(), 1000, () => get_ram_info()) + + return ( + <box class="status-box mem"> + <label label={info.as(i => `Mem: ${Math.round((i.used / i.total) * 100)}% `)} /> + </box> + ) } diff --git a/widget/time.tsx b/widget/time.tsx index 0464568..13f2661 100644 --- a/widget/time.tsx +++ b/widget/time.tsx @@ -1,12 +1,6 @@ -// 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("%b %e (%a) %H:%M")) -// return true - +import { Gtk } from "ags/gtk4" import { createPoll } from "ags/time" -// }) export default function Time() { const time = createPoll("", 500, () => { return new Date().toLocaleString(undefined, { @@ -14,11 +8,13 @@ export default function Time() { }) }) - return <box class="status-box"> - <label label={time} /> - </box> - // return <box class="status-box"> - // <label label={bind(current_time)} /> - // </box> + return ( + <menubutton class="date-popover status-box"> + <popover> + <Gtk.Calendar /> + </popover> + <label label={time} /> + </menubutton> + ) } diff --git a/widget/title.tsx b/widget/title.tsx index 97b2b52..a3fcc6d 100644 --- a/widget/title.tsx +++ b/widget/title.tsx @@ -1,6 +1,5 @@ import { createPoll } from "ags/time"; import AstalHyprland from "gi://AstalHyprland?version=0.1"; -import { createState } from "gnim"; const hyprland = AstalHyprland.get_default() @@ -8,15 +7,10 @@ function get_title() { return hyprland.focusedClient?.title ?? "" } -// const title = Variable(get_title()).poll(200, () => get_title()) - export default function Title() { const title = createPoll("", 200, () => get_title()) return ( <label label={title} /> ) - // return ( - // <label label={title(t => t)} /> - // ) } diff --git a/widget/workspaces.tsx b/widget/workspaces.tsx index 828ed90..eb6b21c 100644 --- a/widget/workspaces.tsx +++ b/widget/workspaces.tsx @@ -4,32 +4,17 @@ import { createBinding, For } from "gnim" export default function Workspaces({ monitor }: { monitor: Gdk.Monitor }) { const hypr = AstalHyprland.get_default() - const workspaces = createBinding(hypr, "workspaces").as(wss => wss.sort((a, b) => a.id - b.id)) + const workspaces = createBinding(hypr, "workspaces").as(wss => wss.filter((ws) => ws.monitor.model === monitor.model).sort((a, b) => a.id - b.id)) return ( <box class="workspaces"> <For each={workspaces}> {(ws) => ( - <button class={createBinding(hypr, "focusedWorkspace").as((fw) => ws === fw ? "focused" : "")}> + <button class={createBinding(hypr, "focusedWorkspace").as((fw) => ws === fw ? "focused" : "")} onClicked={() => ws.focus()}> <label label={ws.id.toString()} /> </button> )} </For> </box> ) - // return <box className="workspaces"> - // {bind(hypr, "workspaces").as(wss => wss.filter(ws => !(ws.id >= -99 && ws.id <= -2)) - // .sort((a, b) => a.id - b.id) - // .map(ws => { - // if (ws.monitor.model !== monitor.model) return <></> - // return ( - // <button - // className={bind(hypr, "focusedWorkspace").as(fw => - // ws === fw ? "focused" : "")} - // onClicked={() => ws.focus()}> - // {ws.id} - // </button> - // ) - // }))} - // </box> }