Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c724c853f5 | |||
| 535c8a3154 | |||
| 6140123cbc |
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"WebFetch(domain:elkowar.github.io)",
|
||||||
|
"WebFetch(domain:github.com)",
|
||||||
|
"WebFetch(domain:raw.githubusercontent.com)",
|
||||||
|
"WebSearch",
|
||||||
|
"WebFetch(domain:deepwiki.com)",
|
||||||
|
"Bash(amdgpu_top -J -s 1000 -d 1)",
|
||||||
|
"Bash(amdgpu_top -J -s 1000 -n 1)",
|
||||||
|
"Bash(sensors -j)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
-45
@@ -1,48 +1,5 @@
|
|||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"ags": {
|
|
||||||
"inputs": {
|
|
||||||
"astal": [
|
|
||||||
"astal"
|
|
||||||
],
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1775689345,
|
|
||||||
"narHash": "sha256-tM3s7CX+tgxlYW0Sk3nzVThg2MHn08foIuMxABupxIs=",
|
|
||||||
"owner": "aylur",
|
|
||||||
"repo": "ags",
|
|
||||||
"rev": "bbee2f18939f1ec7ff720e717cf305e73635628f",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "aylur",
|
|
||||||
"repo": "ags",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"astal": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1780295699,
|
|
||||||
"narHash": "sha256-gt9jeb/HOoiUSOTnE5I9K/B9LEbjJW5k37Xq99HOf/Q=",
|
|
||||||
"owner": "aylur",
|
|
||||||
"repo": "astal",
|
|
||||||
"rev": "271851bbc07748100382ae7caf6ef71c70c01bfc",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "aylur",
|
|
||||||
"repo": "astal",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"base16-schemes": {
|
"base16-schemes": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
@@ -297,8 +254,6 @@
|
|||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"ags": "ags",
|
|
||||||
"astal": "astal",
|
|
||||||
"darwin": "darwin",
|
"darwin": "darwin",
|
||||||
"hardware": "hardware",
|
"hardware": "hardware",
|
||||||
"home-manager": "home-manager",
|
"home-manager": "home-manager",
|
||||||
|
|||||||
@@ -34,17 +34,6 @@
|
|||||||
url = "github:nix-community/nixos-vscode-server";
|
url = "github:nix-community/nixos-vscode-server";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
|
||||||
astal = {
|
|
||||||
url = "github:aylur/astal";
|
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
};
|
|
||||||
|
|
||||||
ags = {
|
|
||||||
url = "github:aylur/ags";
|
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
inputs.astal.follows = "astal";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = inputs:
|
outputs = inputs:
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
{ inputs, lib, config, pkgs, ... }:
|
|
||||||
let
|
|
||||||
colorsScss = ''
|
|
||||||
$base00: #${config.colorScheme.palette.base00};
|
|
||||||
$base01: #${config.colorScheme.palette.base01};
|
|
||||||
$base02: #${config.colorScheme.palette.base02};
|
|
||||||
$base03: #${config.colorScheme.palette.base03};
|
|
||||||
$base04: #${config.colorScheme.palette.base04};
|
|
||||||
$base05: #${config.colorScheme.palette.base05};
|
|
||||||
$base06: #${config.colorScheme.palette.base06};
|
|
||||||
$base07: #${config.colorScheme.palette.base07};
|
|
||||||
$base08: #${config.colorScheme.palette.base08};
|
|
||||||
$base09: #${config.colorScheme.palette.base09};
|
|
||||||
$base0A: #${config.colorScheme.palette.base0A};
|
|
||||||
$base0B: #${config.colorScheme.palette.base0B};
|
|
||||||
$base0C: #${config.colorScheme.palette.base0C};
|
|
||||||
$base0D: #${config.colorScheme.palette.base0D};
|
|
||||||
$base0E: #${config.colorScheme.palette.base0E};
|
|
||||||
$base0F: #${config.colorScheme.palette.base0F};
|
|
||||||
|
|
||||||
|
|
||||||
$fg: $base07;
|
|
||||||
$bg0: $base00;
|
|
||||||
$bg1: $base01;
|
|
||||||
|
|
||||||
$border-color: $base03;
|
|
||||||
$border-color-focus: $base04;
|
|
||||||
$border-radius: ${config.colorScheme.palette.border-radius}px;
|
|
||||||
$border-width: ${config.colorScheme.palette.border-width}px;
|
|
||||||
|
|
||||||
$gaps-screen: ${config.colorScheme.palette.gaps-screen}px;
|
|
||||||
$gaps-window: ${config.colorScheme.palette.gaps-window}px;
|
|
||||||
'';
|
|
||||||
configDir = pkgs.runCommandLocal "ags-config" {} ''
|
|
||||||
mkdir -p "$out"
|
|
||||||
cp -r ${lib.cleanSource ./src}/. "$out/"
|
|
||||||
mkdir -p "$out/css"
|
|
||||||
cat > "$out/css/_colors.scss" <<'EOF'
|
|
||||||
${colorsScss}
|
|
||||||
EOF
|
|
||||||
'';
|
|
||||||
in {
|
|
||||||
|
|
||||||
imports = [ inputs.ags.homeManagerModules.default ];
|
|
||||||
config = lib.mkIf (config.usercfg.wm == "Wayland") {
|
|
||||||
programs.ags = {
|
|
||||||
enable = true;
|
|
||||||
configDir = configDir;
|
|
||||||
extraPackages = with pkgs; [
|
|
||||||
inputs.astal.packages.${pkgs.system}.battery
|
|
||||||
fzf
|
|
||||||
bluez
|
|
||||||
custom.amdgpu_top
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,768 +0,0 @@
|
|||||||
import app from "ags/gtk4/app"
|
|
||||||
import { Astal } from "ags/gtk4"
|
|
||||||
import { createState, onCleanup } from "ags"
|
|
||||||
import { readFileAsync } from "ags/file"
|
|
||||||
import { execAsync } from "ags/process"
|
|
||||||
import { createPoll } from "ags/time"
|
|
||||||
import Gdk from "gi://Gdk?version=4.0"
|
|
||||||
import GLib from "gi://GLib"
|
|
||||||
import Gtk from "gi://Gtk?version=4.0"
|
|
||||||
|
|
||||||
import style from "./style.scss"
|
|
||||||
|
|
||||||
type Workspace = {
|
|
||||||
id: number
|
|
||||||
occupied: boolean
|
|
||||||
focused: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
type BatteryState = {
|
|
||||||
available: boolean
|
|
||||||
percent: number
|
|
||||||
status: string
|
|
||||||
remaining: string
|
|
||||||
icon: string
|
|
||||||
}
|
|
||||||
|
|
||||||
type SystemState = {
|
|
||||||
cpu: number
|
|
||||||
gpu: number
|
|
||||||
memory: number
|
|
||||||
memoryText: string
|
|
||||||
battery: BatteryState
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConnectivityState = {
|
|
||||||
wifi: {
|
|
||||||
label: string
|
|
||||||
detail: string
|
|
||||||
icon: string
|
|
||||||
}
|
|
||||||
ethernet: {
|
|
||||||
label: string
|
|
||||||
detail: string
|
|
||||||
icon: string
|
|
||||||
}
|
|
||||||
bluetooth: {
|
|
||||||
label: string
|
|
||||||
detail: string
|
|
||||||
icon: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ClockState = {
|
|
||||||
hour: string
|
|
||||||
minute: string
|
|
||||||
month: string
|
|
||||||
year: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const SYSTEM_INIT: SystemState = {
|
|
||||||
cpu: 0,
|
|
||||||
gpu: 0,
|
|
||||||
memory: 0,
|
|
||||||
memoryText: "0 / 0 GiB",
|
|
||||||
battery: {
|
|
||||||
available: false,
|
|
||||||
percent: 0,
|
|
||||||
status: "No battery",
|
|
||||||
remaining: "",
|
|
||||||
icon: "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const CONNECTIVITY_INIT: ConnectivityState = {
|
|
||||||
wifi: {
|
|
||||||
label: "Wi-Fi",
|
|
||||||
detail: "Unavailable",
|
|
||||||
icon: "",
|
|
||||||
},
|
|
||||||
ethernet: {
|
|
||||||
label: "Ethernet",
|
|
||||||
detail: "Disconnected",
|
|
||||||
icon: "",
|
|
||||||
},
|
|
||||||
bluetooth: {
|
|
||||||
label: "Bluetooth",
|
|
||||||
detail: "Off",
|
|
||||||
icon: "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const CLOCK_INIT: ClockState = {
|
|
||||||
hour: "--",
|
|
||||||
minute: "--",
|
|
||||||
month: "--",
|
|
||||||
year: "--",
|
|
||||||
}
|
|
||||||
|
|
||||||
let previousCpuSample: { total: number; idle: number } | null = null
|
|
||||||
const [powerMenuVisible, setPowerMenuVisible] = createState(false)
|
|
||||||
|
|
||||||
function attachHover(widget: Gtk.Widget, onEnter: () => void, onLeave: () => void) {
|
|
||||||
const controller = new Gtk.EventControllerMotion()
|
|
||||||
controller.connect("enter", onEnter)
|
|
||||||
controller.connect("leave", onLeave)
|
|
||||||
widget.add_controller(controller)
|
|
||||||
}
|
|
||||||
|
|
||||||
function clampPercent(value: number) {
|
|
||||||
if (!Number.isFinite(value)) {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return Math.max(0, Math.min(100, Math.round(value)))
|
|
||||||
}
|
|
||||||
|
|
||||||
async function commandOrEmpty(command: string | string[]) {
|
|
||||||
try {
|
|
||||||
return (await execAsync(command)).trim()
|
|
||||||
} catch {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function readText(path: string) {
|
|
||||||
try {
|
|
||||||
return (await readFileAsync(path)).trim()
|
|
||||||
} catch {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function readNumber(path: string) {
|
|
||||||
const value = Number(await readText(path))
|
|
||||||
return Number.isFinite(value) ? value : 0
|
|
||||||
}
|
|
||||||
|
|
||||||
function run(command: string) {
|
|
||||||
execAsync(["bash", "-lc", command]).catch((error) => console.error(error))
|
|
||||||
}
|
|
||||||
|
|
||||||
function focusWorkspace(id: number) {
|
|
||||||
run(`hyprctl dispatch workspace ${id}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
function mediaAction(action: string) {
|
|
||||||
run(`playerctl ${action}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function readWeather() {
|
|
||||||
const line = await commandOrEmpty([
|
|
||||||
"bash",
|
|
||||||
"-lc",
|
|
||||||
"curl -fsS 'https://wttr.in/?format=%l:+%C+%t' 2>/dev/null | head -n1",
|
|
||||||
])
|
|
||||||
|
|
||||||
return line || "Weather unavailable"
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderCalendar(now: Date) {
|
|
||||||
const monthLabel = now.toLocaleString("en-US", { month: "short" }).toUpperCase()
|
|
||||||
const year = now.getFullYear()
|
|
||||||
const header = `${monthLabel} ${year}`
|
|
||||||
const weekdays = "MO TU WE TH FR SA SU"
|
|
||||||
const first = new Date(year, now.getMonth(), 1)
|
|
||||||
const lastDay = new Date(year, now.getMonth() + 1, 0).getDate()
|
|
||||||
const offset = (first.getDay() + 6) % 7
|
|
||||||
const slots = Array.from({ length: offset + lastDay }, (_, index) =>
|
|
||||||
index < offset ? " " : String(index - offset + 1).padStart(2, " "),
|
|
||||||
)
|
|
||||||
|
|
||||||
const rows = []
|
|
||||||
for (let index = 0; index < slots.length; index += 7) {
|
|
||||||
rows.push(slots.slice(index, index + 7).join(" "))
|
|
||||||
}
|
|
||||||
|
|
||||||
return [header, weekdays, ...rows].join("\n").replace(/ /g, "\u2007")
|
|
||||||
}
|
|
||||||
|
|
||||||
function workspaceIcon(workspace: Workspace) {
|
|
||||||
if (workspace.focused) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if (workspace.occupied) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
function wifiIcon(detail: string) {
|
|
||||||
if (detail === "Off") {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
const strength = Number(detail.split("%", 1)[0])
|
|
||||||
|
|
||||||
if (!Number.isFinite(strength) || strength <= 0) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strength < 25) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strength < 50) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strength < 75) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
function batteryIcon(percent: number, status: string) {
|
|
||||||
if (status === "Charging") {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if (percent <= 10) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if (percent <= 20) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if (percent <= 30) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if (percent <= 40) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if (percent <= 50) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if (percent <= 60) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if (percent <= 70) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if (percent <= 80) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if (percent <= 90) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatHours(hours: number) {
|
|
||||||
const totalMinutes = Math.max(0, Math.round(hours * 60))
|
|
||||||
const hh = String(Math.floor(totalMinutes / 60)).padStart(2, "0")
|
|
||||||
const mm = String(totalMinutes % 60).padStart(2, "0")
|
|
||||||
return `${hh}:${mm}`
|
|
||||||
}
|
|
||||||
|
|
||||||
async function readWorkspaces() {
|
|
||||||
const [workspacesRaw, monitorsRaw] = await Promise.all([
|
|
||||||
commandOrEmpty(["hyprctl", "-j", "workspaces"]),
|
|
||||||
commandOrEmpty(["hyprctl", "-j", "monitors"]),
|
|
||||||
])
|
|
||||||
|
|
||||||
const workspaces = workspacesRaw ? JSON.parse(workspacesRaw) : []
|
|
||||||
const monitors = monitorsRaw ? JSON.parse(monitorsRaw) : []
|
|
||||||
const focused = monitors.find((monitor: any) => monitor.focused)?.activeWorkspace?.id ?? 1
|
|
||||||
const occupied = new Set(
|
|
||||||
workspaces
|
|
||||||
.map((workspace: any) => Number(workspace.id))
|
|
||||||
.filter((id: number) => Number.isFinite(id) && id > 0),
|
|
||||||
)
|
|
||||||
|
|
||||||
return Array.from({ length: 10 }, (_, index) => {
|
|
||||||
const id = index + 1
|
|
||||||
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
occupied: occupied.has(id),
|
|
||||||
focused: focused === id,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async function readSystem() {
|
|
||||||
const [statRaw, meminfoRaw, gpuRaw] = await Promise.all([
|
|
||||||
readText("/proc/stat"),
|
|
||||||
readText("/proc/meminfo"),
|
|
||||||
commandOrEmpty(["amdgpu_top", "-J", "-n", "1"]),
|
|
||||||
])
|
|
||||||
|
|
||||||
let cpu = 0
|
|
||||||
const statLine = statRaw.split("\n")[0] ?? ""
|
|
||||||
const cpuValues = statLine
|
|
||||||
.split(/\s+/)
|
|
||||||
.slice(1)
|
|
||||||
.map((value) => Number(value))
|
|
||||||
.filter((value) => Number.isFinite(value))
|
|
||||||
|
|
||||||
if (cpuValues.length >= 4) {
|
|
||||||
const total = cpuValues.reduce((sum, value) => sum + value, 0)
|
|
||||||
const idle = (cpuValues[3] ?? 0) + (cpuValues[4] ?? 0)
|
|
||||||
|
|
||||||
if (previousCpuSample) {
|
|
||||||
const totalDelta = total - previousCpuSample.total
|
|
||||||
const idleDelta = idle - previousCpuSample.idle
|
|
||||||
|
|
||||||
if (totalDelta > 0) {
|
|
||||||
cpu = clampPercent(((totalDelta - idleDelta) / totalDelta) * 100)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
previousCpuSample = { total, idle }
|
|
||||||
}
|
|
||||||
|
|
||||||
const memTotal = Number(meminfoRaw.match(/^MemTotal:\s+(\d+)/m)?.[1] ?? 0)
|
|
||||||
const memAvailable = Number(meminfoRaw.match(/^MemAvailable:\s+(\d+)/m)?.[1] ?? 0)
|
|
||||||
const memUsed = Math.max(0, memTotal - memAvailable)
|
|
||||||
const memory = memTotal > 0 ? clampPercent((memUsed / memTotal) * 100) : 0
|
|
||||||
const memoryText = `${(memUsed / 1024 / 1024).toFixed(1)} / ${(memTotal / 1024 / 1024).toFixed(1)} GiB`
|
|
||||||
|
|
||||||
let gpu = 0
|
|
||||||
|
|
||||||
if (gpuRaw) {
|
|
||||||
try {
|
|
||||||
const gpuData = JSON.parse(gpuRaw)
|
|
||||||
const device = gpuData.devices?.[0] ?? {}
|
|
||||||
|
|
||||||
gpu =
|
|
||||||
clampPercent(
|
|
||||||
Number(
|
|
||||||
device.gpu_activity?.GFX?.value ??
|
|
||||||
device.GRBM2?.["Command Processor - Graphics"]?.value ??
|
|
||||||
device.GRBM2?.["CommandProcessor-Graphics"]?.value ??
|
|
||||||
0,
|
|
||||||
),
|
|
||||||
) || 0
|
|
||||||
} catch {
|
|
||||||
gpu = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const batteryPresent = Boolean(await readText("/sys/class/power_supply/BAT0/status"))
|
|
||||||
let battery = SYSTEM_INIT.battery
|
|
||||||
|
|
||||||
if (batteryPresent) {
|
|
||||||
const [status, percent, powerNow, energyNow, energyFull] = await Promise.all([
|
|
||||||
readText("/sys/class/power_supply/BAT0/status"),
|
|
||||||
readNumber("/sys/class/power_supply/BAT0/capacity"),
|
|
||||||
readNumber("/sys/class/power_supply/BAT0/power_now"),
|
|
||||||
readNumber("/sys/class/power_supply/BAT0/energy_now"),
|
|
||||||
readNumber("/sys/class/power_supply/BAT0/energy_full"),
|
|
||||||
])
|
|
||||||
|
|
||||||
let remaining = status
|
|
||||||
|
|
||||||
if (powerNow > 0 && energyNow > 0 && energyFull > 0) {
|
|
||||||
const hours =
|
|
||||||
status === "Charging"
|
|
||||||
? (energyFull - energyNow) / powerNow
|
|
||||||
: status === "Discharging"
|
|
||||||
? energyNow / powerNow
|
|
||||||
: 0
|
|
||||||
|
|
||||||
if (hours > 0) {
|
|
||||||
remaining = `${formatHours(hours)} ${status === "Charging" ? "to full" : "left"}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
battery = {
|
|
||||||
available: true,
|
|
||||||
percent: clampPercent(percent),
|
|
||||||
status,
|
|
||||||
remaining,
|
|
||||||
icon: batteryIcon(percent, status),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
cpu,
|
|
||||||
gpu,
|
|
||||||
memory,
|
|
||||||
memoryText,
|
|
||||||
battery,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function readConnectivity() {
|
|
||||||
const deviceStatus = await commandOrEmpty(["nmcli", "-t", "-f", "DEVICE,TYPE,STATE,CONNECTION", "device", "status"])
|
|
||||||
const devices = deviceStatus
|
|
||||||
.split("\n")
|
|
||||||
.map((line) => line.trim())
|
|
||||||
.filter(Boolean)
|
|
||||||
.map((line) => {
|
|
||||||
const [device, type, state, connection] = line.split(":")
|
|
||||||
return { device, type, state, connection }
|
|
||||||
})
|
|
||||||
|
|
||||||
const wifiDevice = devices.find((device) => device.type === "wifi")
|
|
||||||
const ethernetDevice = devices.find((device) => device.type === "ethernet")
|
|
||||||
|
|
||||||
let wifi = CONNECTIVITY_INIT.wifi
|
|
||||||
|
|
||||||
if (wifiDevice) {
|
|
||||||
if (wifiDevice.state === "connected") {
|
|
||||||
const wifiList = await commandOrEmpty([
|
|
||||||
"nmcli",
|
|
||||||
"-t",
|
|
||||||
"-f",
|
|
||||||
"IN-USE,SIGNAL,SSID",
|
|
||||||
"device",
|
|
||||||
"wifi",
|
|
||||||
"list",
|
|
||||||
"--rescan",
|
|
||||||
"no",
|
|
||||||
])
|
|
||||||
const activeLine =
|
|
||||||
wifiList
|
|
||||||
.split("\n")
|
|
||||||
.map((line) => line.trim())
|
|
||||||
.find((line) => line.startsWith("*:")) ?? ""
|
|
||||||
const [, signalRaw = "0", ssid = wifiDevice.connection] = activeLine.split(":")
|
|
||||||
const signal = clampPercent(Number(signalRaw))
|
|
||||||
|
|
||||||
wifi = {
|
|
||||||
label: "Wi-Fi",
|
|
||||||
detail: `${signal}% ${ssid || wifiDevice.connection}`,
|
|
||||||
icon: wifiIcon(`${signal}%`),
|
|
||||||
}
|
|
||||||
} else if (wifiDevice.state === "disconnected") {
|
|
||||||
wifi = {
|
|
||||||
label: "Wi-Fi",
|
|
||||||
detail: "Idle",
|
|
||||||
icon: "",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ethernet =
|
|
||||||
ethernetDevice && ethernetDevice.state === "connected"
|
|
||||||
? {
|
|
||||||
label: "Ethernet",
|
|
||||||
detail: ethernetDevice.connection || ethernetDevice.device,
|
|
||||||
icon: "",
|
|
||||||
}
|
|
||||||
: CONNECTIVITY_INIT.ethernet
|
|
||||||
|
|
||||||
const bluetoothShow = await commandOrEmpty(["bash", "-lc", "bluetoothctl show 2>/dev/null || true"])
|
|
||||||
const bluetoothDevices = await commandOrEmpty([
|
|
||||||
"bash",
|
|
||||||
"-lc",
|
|
||||||
"bluetoothctl devices Connected 2>/dev/null | cut -d' ' -f3- || true",
|
|
||||||
])
|
|
||||||
const bluetoothPowered = /Powered:\s+yes/.test(bluetoothShow)
|
|
||||||
const connectedDevices = bluetoothDevices.split("\n").map((line) => line.trim()).filter(Boolean)
|
|
||||||
|
|
||||||
const bluetooth = bluetoothPowered
|
|
||||||
? {
|
|
||||||
label: "Bluetooth",
|
|
||||||
detail: connectedDevices[0] ?? "Ready",
|
|
||||||
icon: connectedDevices.length > 0 ? "" : "",
|
|
||||||
}
|
|
||||||
: CONNECTIVITY_INIT.bluetooth
|
|
||||||
|
|
||||||
return {
|
|
||||||
wifi,
|
|
||||||
ethernet,
|
|
||||||
bluetooth,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function Section(props: { title: string; className?: string; children?: JSX.Element | Array<JSX.Element> }) {
|
|
||||||
return (
|
|
||||||
<box class={`section ${props.className ?? ""}`} orientation={Gtk.Orientation.VERTICAL}>
|
|
||||||
<label class="section-title" xalign={0} label={props.title} />
|
|
||||||
<box class="section-body" orientation={Gtk.Orientation.VERTICAL} spacing={0}>
|
|
||||||
{props.children}
|
|
||||||
</box>
|
|
||||||
</box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function StatRow(props: {
|
|
||||||
icon: any
|
|
||||||
label: any
|
|
||||||
value: any
|
|
||||||
fraction?: any
|
|
||||||
visible?: any
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<box class="stat-row" orientation={Gtk.Orientation.VERTICAL} visible={props.visible ?? true}>
|
|
||||||
<box class="stat-head" orientation={Gtk.Orientation.VERTICAL} spacing={4}>
|
|
||||||
<label class="stat-icon" label={props.icon} />
|
|
||||||
</box>
|
|
||||||
<levelbar class="stat-bar" minValue={0} maxValue={1} value={props.fraction ?? 0} />
|
|
||||||
</box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function StatusRow(props: { icon: any; label: any; detail: any }) {
|
|
||||||
return (
|
|
||||||
<box class="status-row">
|
|
||||||
<label class="status-icon" label={props.icon} />
|
|
||||||
</box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function ActionButton(props: { icon: string; tooltip: string; command: string }) {
|
|
||||||
return (
|
|
||||||
<button class="action-button" tooltipText={props.tooltip} onClicked={() => run(props.command)}>
|
|
||||||
<label class="action-icon" label={props.icon} />
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function WorkspacesSection() {
|
|
||||||
const workspaces = createPoll<Workspace[]>([], 1500, readWorkspaces)
|
|
||||||
const workspaceIds = Array.from({ length: 10 }, (_, index) => index + 1)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Section title="Desktops" className="desktops-section">
|
|
||||||
<box class="workspace-stack" orientation={Gtk.Orientation.VERTICAL} spacing={2}>
|
|
||||||
{workspaceIds.map((id) => {
|
|
||||||
const workspace = workspaces(
|
|
||||||
(items) => items.find((item) => item.id === id) ?? { id, occupied: false, focused: false },
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
class={workspace((item) =>
|
|
||||||
item.focused
|
|
||||||
? "workspace-button focused"
|
|
||||||
: item.occupied
|
|
||||||
? "workspace-button occupied"
|
|
||||||
: "workspace-button",
|
|
||||||
)}
|
|
||||||
tooltipText={`Workspace ${id}`}
|
|
||||||
onClicked={() => focusWorkspace(id)}
|
|
||||||
>
|
|
||||||
<label class="workspace-icon" label={workspace((item) => workspaceIcon(item))} />
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</box>
|
|
||||||
</Section>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function MediaSection() {
|
|
||||||
return (
|
|
||||||
<Section title="Media">
|
|
||||||
<box class="media-controls" orientation={Gtk.Orientation.VERTICAL} spacing={4}>
|
|
||||||
<button class="mini-button" tooltipText="Previous" onClicked={() => mediaAction("previous")}>
|
|
||||||
<label label="" />
|
|
||||||
</button>
|
|
||||||
<button class="mini-button" tooltipText="Play / Pause" onClicked={() => mediaAction("play-pause")}>
|
|
||||||
<label label="" />
|
|
||||||
</button>
|
|
||||||
<button class="mini-button" tooltipText="Next" onClicked={() => mediaAction("next")}>
|
|
||||||
<label label="" />
|
|
||||||
</button>
|
|
||||||
</box>
|
|
||||||
</Section>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function SystemSection() {
|
|
||||||
const system = createPoll<SystemState>(SYSTEM_INIT, 2500, readSystem)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Section title="System">
|
|
||||||
<StatRow
|
|
||||||
icon=""
|
|
||||||
label="CPU"
|
|
||||||
value={system((value) => `${value.cpu}%`)}
|
|
||||||
fraction={system((value) => value.cpu / 100)}
|
|
||||||
/>
|
|
||||||
<StatRow
|
|
||||||
icon=""
|
|
||||||
label="GPU"
|
|
||||||
value={system((value) => `${value.gpu}%`)}
|
|
||||||
fraction={system((value) => value.gpu / 100)}
|
|
||||||
/>
|
|
||||||
<StatRow
|
|
||||||
icon=""
|
|
||||||
label="RAM"
|
|
||||||
value={system((value) => `${value.memory}%`)}
|
|
||||||
fraction={system((value) => value.memory / 100)}
|
|
||||||
/>
|
|
||||||
<StatRow
|
|
||||||
icon={system((value) => value.battery.icon)}
|
|
||||||
label={system((value) => value.battery.status)}
|
|
||||||
value={system((value) => `${value.battery.percent}%`)}
|
|
||||||
fraction={system((value) => value.battery.percent / 100)}
|
|
||||||
visible={system((value) => value.battery.available)}
|
|
||||||
/>
|
|
||||||
</Section>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function ConnectivitySection() {
|
|
||||||
const connectivity = createPoll<ConnectivityState>(CONNECTIVITY_INIT, 5000, readConnectivity)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Section title="Network">
|
|
||||||
<StatusRow
|
|
||||||
icon={connectivity((value) => value.wifi.icon)}
|
|
||||||
label={connectivity((value) => value.wifi.label)}
|
|
||||||
detail={connectivity((value) => value.wifi.detail)}
|
|
||||||
/>
|
|
||||||
<StatusRow
|
|
||||||
icon={connectivity((value) => value.bluetooth.icon)}
|
|
||||||
label={connectivity((value) => value.bluetooth.label)}
|
|
||||||
detail={connectivity((value) => value.bluetooth.detail)}
|
|
||||||
/>
|
|
||||||
<StatusRow
|
|
||||||
icon={connectivity((value) => value.ethernet.icon)}
|
|
||||||
label={connectivity((value) => value.ethernet.label)}
|
|
||||||
detail={connectivity((value) => value.ethernet.detail)}
|
|
||||||
/>
|
|
||||||
</Section>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function WidgetsSection() {
|
|
||||||
return (
|
|
||||||
<Section title="Widgets">
|
|
||||||
<box class="action-row" orientation={Gtk.Orientation.VERTICAL} spacing={4}>
|
|
||||||
<ActionButton icon="" tooltip="Launcher" command="wofi --show drun" />
|
|
||||||
<ActionButton icon="" tooltip="Terminal" command="kitty" />
|
|
||||||
<ActionButton icon="" tooltip="Mute speakers" command="wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle" />
|
|
||||||
<ActionButton icon="" tooltip="Mute microphone" command="wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle" />
|
|
||||||
</box>
|
|
||||||
</Section>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function ClockPowerSection() {
|
|
||||||
const clock = createPoll<ClockState>(CLOCK_INIT, 1000, () => {
|
|
||||||
const now = GLib.DateTime.new_now_local()
|
|
||||||
|
|
||||||
return {
|
|
||||||
hour: now.format("%H") ?? "--",
|
|
||||||
minute: now.format("%M") ?? "--",
|
|
||||||
month: now.format("%m") ?? "--",
|
|
||||||
year: now.format("%y") ?? "--",
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const [clockHovered, setClockHovered] = createState(false)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Section title="Time / Power" className="clock-section">
|
|
||||||
<button
|
|
||||||
class="clock-trigger"
|
|
||||||
tooltipText="Open power menu"
|
|
||||||
onClicked={() => setPowerMenuVisible(true)}
|
|
||||||
$={(self) => attachHover(self, () => setClockHovered(true), () => setClockHovered(false))}
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
class="clock-time"
|
|
||||||
justify={Gtk.Justification.CENTER}
|
|
||||||
label={clock((value) =>
|
|
||||||
clockHovered() ? `${value.month}\n${value.year}` : `${value.hour}\n${value.minute}`,
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</Section>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function PowerMenu({ gdkmonitor }: { gdkmonitor: Gdk.Monitor }) {
|
|
||||||
const weather = createPoll("Weather unavailable", 900000, readWeather)
|
|
||||||
const calendar = createPoll(renderCalendar(new Date()), 60000, () => renderCalendar(new Date()))
|
|
||||||
const { TOP, BOTTOM, LEFT, RIGHT } = Astal.WindowAnchor
|
|
||||||
|
|
||||||
return (
|
|
||||||
<window
|
|
||||||
visible={powerMenuVisible}
|
|
||||||
name="ags-power-menu"
|
|
||||||
namespace="ags-power-menu"
|
|
||||||
gdkmonitor={gdkmonitor}
|
|
||||||
application={app}
|
|
||||||
layer={Astal.Layer.OVERLAY}
|
|
||||||
keymode={Astal.Keymode.ON_DEMAND}
|
|
||||||
exclusivity={Astal.Exclusivity.IGNORE}
|
|
||||||
anchor={TOP | BOTTOM | LEFT | RIGHT}
|
|
||||||
margin={0}
|
|
||||||
>
|
|
||||||
<overlay hexpand vexpand>
|
|
||||||
<button class="power-backdrop" hexpand vexpand onClicked={() => setPowerMenuVisible(false)} />
|
|
||||||
<box type="overlay" class="power-menu" orientation={Gtk.Orientation.VERTICAL} halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER}>
|
|
||||||
<label class="power-weather" wrap justify={Gtk.Justification.CENTER} label={weather((value) => value)} />
|
|
||||||
<label class="power-calendar" justify={Gtk.Justification.LEFT} xalign={0} label={calendar((value) => value)} />
|
|
||||||
<box class="power-actions" spacing={8}>
|
|
||||||
<ActionButton icon="" tooltip="Lock" command="swaylock" />
|
|
||||||
<ActionButton icon="" tooltip="Suspend" command="systemctl suspend" />
|
|
||||||
<ActionButton icon="" tooltip="Reboot" command="systemctl reboot" />
|
|
||||||
<ActionButton icon="" tooltip="Power off" command="systemctl poweroff" />
|
|
||||||
</box>
|
|
||||||
</box>
|
|
||||||
</overlay>
|
|
||||||
</window>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function RightBar({ gdkmonitor }: { gdkmonitor: Gdk.Monitor }) {
|
|
||||||
let window: Astal.Window
|
|
||||||
const { TOP, BOTTOM, RIGHT } = Astal.WindowAnchor
|
|
||||||
|
|
||||||
onCleanup(() => {
|
|
||||||
window.destroy()
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<window
|
|
||||||
$={(self) => (window = self)}
|
|
||||||
visible
|
|
||||||
name={`ags-right-bar-${gdkmonitor.connector}`}
|
|
||||||
namespace="ags-right-bar"
|
|
||||||
gdkmonitor={gdkmonitor}
|
|
||||||
application={app}
|
|
||||||
anchor={TOP | BOTTOM | RIGHT}
|
|
||||||
exclusivity={Astal.Exclusivity.EXCLUSIVE}
|
|
||||||
marginRight={0}
|
|
||||||
marginTop={0}
|
|
||||||
marginBottom={0}
|
|
||||||
>
|
|
||||||
<box class="bar-shell" orientation={Gtk.Orientation.VERTICAL}>
|
|
||||||
<WorkspacesSection />
|
|
||||||
<MediaSection />
|
|
||||||
<SystemSection />
|
|
||||||
<ConnectivitySection />
|
|
||||||
<WidgetsSection />
|
|
||||||
<box vexpand />
|
|
||||||
<ClockPowerSection />
|
|
||||||
</box>
|
|
||||||
</window>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
app.start({
|
|
||||||
css: style,
|
|
||||||
gtkTheme: "Adwaita",
|
|
||||||
main() {
|
|
||||||
const [monitor] = app.get_monitors()
|
|
||||||
|
|
||||||
if (!monitor) {
|
|
||||||
throw new Error("No monitor available for AGS bar")
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<RightBar gdkmonitor={monitor} />
|
|
||||||
<PowerMenu gdkmonitor={monitor} />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -1,176 +0,0 @@
|
|||||||
@use "sass:color";
|
|
||||||
@use "./css/_colors.scss" as *;
|
|
||||||
|
|
||||||
* {
|
|
||||||
color: $fg;
|
|
||||||
font-family: "IBM Plex Sans", "Symbols Nerd Font Mono";
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
window {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bar-shell {
|
|
||||||
min-width: 42px;
|
|
||||||
padding: 4px 0 4px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section {
|
|
||||||
background: rgba(color.channel($bg1, "red", $space: rgb), color.channel($bg1, "green", $space: rgb), color.channel($bg1, "blue", $space: rgb), 0.92);
|
|
||||||
border: $border-width solid $border-color;
|
|
||||||
border-right: 0;
|
|
||||||
border-radius: $border-radius 0 0 $border-radius;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
padding: 6px 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-title {
|
|
||||||
min-height: 0;
|
|
||||||
font-size: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-body {
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspace-stack {
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspace-button,
|
|
||||||
.mini-button,
|
|
||||||
.action-button,
|
|
||||||
.clock-trigger {
|
|
||||||
background: transparent;
|
|
||||||
border: 0;
|
|
||||||
border-radius: calc($border-radius - 2px);
|
|
||||||
box-shadow: none;
|
|
||||||
min-height: 0;
|
|
||||||
min-width: 0;
|
|
||||||
padding: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspace-button:hover,
|
|
||||||
.mini-button:hover,
|
|
||||||
.action-button:hover,
|
|
||||||
.clock-trigger:hover {
|
|
||||||
background: rgba(color.channel($base03, "red", $space: rgb), color.channel($base03, "green", $space: rgb), color.channel($base03, "blue", $space: rgb), 0.35);
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspace-button.focused {
|
|
||||||
background: rgba(color.channel($base0D, "red", $space: rgb), color.channel($base0D, "green", $space: rgb), color.channel($base0D, "blue", $space: rgb), 0.16);
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspace-button.occupied .workspace-icon {
|
|
||||||
color: $base05;
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspace-button.focused .workspace-icon {
|
|
||||||
color: $base0D;
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspace-icon,
|
|
||||||
.action-icon {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.media-controls,
|
|
||||||
.action-row {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-row {
|
|
||||||
margin-bottom: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-row:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-icon,
|
|
||||||
.status-icon {
|
|
||||||
color: $base0A;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-head,
|
|
||||||
.status-row {
|
|
||||||
min-width: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-bar {
|
|
||||||
margin-top: 3px;
|
|
||||||
min-height: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-bar trough {
|
|
||||||
background: rgba(red($base02), green($base02), blue($base02), 0.85);
|
|
||||||
border-radius: 99px;
|
|
||||||
min-height: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-bar block.filled {
|
|
||||||
background: $base0D;
|
|
||||||
border-radius: 99px;
|
|
||||||
min-height: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.clock-section {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.clock-trigger {
|
|
||||||
padding: 6px 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.clock-time {
|
|
||||||
color: $base0E;
|
|
||||||
font-family: "IBM Plex Mono", "Symbols Nerd Font Mono";
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 800;
|
|
||||||
min-width: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.power-backdrop {
|
|
||||||
background: rgba(color.channel($base00, "red", $space: rgb), color.channel($base00, "green", $space: rgb), color.channel($base00, "blue", $space: rgb), 0.42);
|
|
||||||
border: 0;
|
|
||||||
border-radius: 0;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.power-menu {
|
|
||||||
background: rgba(color.channel($bg1, "red", $space: rgb), color.channel($bg1, "green", $space: rgb), color.channel($bg1, "blue", $space: rgb), 0.94);
|
|
||||||
border: $border-width solid $border-color-focus;
|
|
||||||
border-radius: 18px;
|
|
||||||
min-width: 320px;
|
|
||||||
padding: 20px 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.power-weather {
|
|
||||||
color: $base0C;
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 700;
|
|
||||||
margin-bottom: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.power-calendar {
|
|
||||||
font-family: "IBM Plex Mono", "Symbols Nerd Font Mono";
|
|
||||||
font-size: 13px;
|
|
||||||
margin-bottom: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.power-actions {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.power-actions .action-button {
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
@@ -1 +1 @@
|
|||||||
{ ... }: { imports = [ ./dunst ./eww ./kanshi ./waylock ./wofi ./ags ]; }
|
{ ... }: { imports = [ ./dunst ./eww ./kanshi ./waylock ./wofi ]; }
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
$base00: #000000;
|
||||||
|
$base01: #060a0f;
|
||||||
|
$base02: #212c38;
|
||||||
|
$base03: #3f5268;
|
||||||
|
$base04: #617b9a;
|
||||||
|
$base05: #90a7c1;
|
||||||
|
$base06: #c9d3df;
|
||||||
|
$base07: #fcfcfc;
|
||||||
|
$base08: #ffac56;
|
||||||
|
$base09: #feea74;
|
||||||
|
$base0A: #bffe8a;
|
||||||
|
$base0B: #4cfefa;
|
||||||
|
$base0C: #62acfd;
|
||||||
|
$base0D: #9b9bfd;
|
||||||
|
$base0E: #fe9bda;
|
||||||
|
$base0F: #fc8999;
|
||||||
|
|
||||||
|
|
||||||
|
$fg: $base07;
|
||||||
|
$bg0: $base00;
|
||||||
|
$bg1: $base01;
|
||||||
|
|
||||||
|
$border-color: $base03;
|
||||||
|
$border-color-focus: $base04;
|
||||||
|
$border-radius: 8px;
|
||||||
|
$border-width: 2px;
|
||||||
|
|
||||||
|
$gaps-screen: 8px;
|
||||||
|
$gaps-window: 4px;
|
||||||
@@ -1,8 +1,24 @@
|
|||||||
|
// Bar icons
|
||||||
|
.net-icon { font-size: 14px; padding: 3pt 0; }
|
||||||
|
.net-active { color: $base07; }
|
||||||
|
.net-dim { color: $base02; }
|
||||||
|
.blt-on { color: $base07; }
|
||||||
|
.blt-connected { color: $base0D; }
|
||||||
|
|
||||||
.net {
|
// Popup window section accents
|
||||||
color: $base07;
|
.wifi-accent { background-color: $base0C; }
|
||||||
}
|
.eth-accent { background-color: $base0B; }
|
||||||
|
.blt-accent { background-color: $base0D; }
|
||||||
|
|
||||||
.blt {
|
// Netinfo rows
|
||||||
color: $base0C;
|
.netinfo-row { margin-bottom: 3pt; }
|
||||||
}
|
.netinfo-label { font-size: 0.72em; color: $base04; min-width: 60px; }
|
||||||
|
.netinfo-value { font-size: 0.72em; color: $base05; }
|
||||||
|
.netinfo-dim { font-size: 0.72em; color: $base03; margin-bottom: 4pt; }
|
||||||
|
|
||||||
|
// Bluetooth device list
|
||||||
|
.bt-device-row { margin-bottom: 4pt; }
|
||||||
|
.bt-device-name { font-size: 0.78em; color: $base05; }
|
||||||
|
.bt-device-btn { padding: 2pt 6pt; border-radius: $border-radius; font-size: 1em; }
|
||||||
|
.bt-btn-on { color: $base0D; }
|
||||||
|
.bt-btn-off { color: $base03; }
|
||||||
|
|||||||
@@ -1,69 +1,186 @@
|
|||||||
|
|
||||||
.cpubar {
|
// Bar module rings
|
||||||
color: $base0C;
|
.cpubar { color: $base0C; }
|
||||||
}
|
.gpubar { color: $base0E; }
|
||||||
|
.membar { color: $base08; }
|
||||||
.gpubar {
|
.batbar { color: $base0B; }
|
||||||
color: $base0E;
|
|
||||||
}
|
.cpubar, .gpubar, .membar, .batbar {
|
||||||
|
background-color: $bg0;
|
||||||
.membar {
|
margin: $gaps-window 0;
|
||||||
color: $base08;
|
|
||||||
}
|
|
||||||
|
|
||||||
.batbar {
|
|
||||||
color: $base0B;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cpubar,
|
|
||||||
.gpubar,
|
|
||||||
.membar,
|
|
||||||
.batbar {
|
|
||||||
background-color: $bg0;
|
|
||||||
margin: $gaps-window 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cpu-core-usage, .gpu-core-usage, .memory-usage {
|
|
||||||
background-color: $bg0;
|
|
||||||
border-radius: $border-radius;
|
|
||||||
padding: 2pt;
|
|
||||||
margin: 1pt;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.cpu-core-usage trough * {
|
|
||||||
background-color: $base0C;
|
|
||||||
border-radius: $border-radius;
|
|
||||||
padding: 2pt;
|
|
||||||
}
|
|
||||||
.gpu-core-usage trough * {
|
|
||||||
background-color: $base0E;
|
|
||||||
border-radius: $border-radius;
|
|
||||||
padding: 2pt;
|
|
||||||
}
|
|
||||||
|
|
||||||
.memory-usage trough * {
|
|
||||||
background-color: $base08;
|
|
||||||
border-radius: $border-radius;
|
|
||||||
padding: 2pt;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.spacer {
|
|
||||||
color: $bg1;
|
|
||||||
padding: $gaps-window;
|
|
||||||
margin:0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Window
|
||||||
.sys-win {
|
.sys-win {
|
||||||
// @include window;
|
padding: 10pt;
|
||||||
// background-color: $bg1;
|
}
|
||||||
// color: $fg;
|
|
||||||
// margin: $gaps-win;
|
.sys-section {
|
||||||
padding: 5pt;
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sys-section-header {
|
||||||
|
margin-bottom: 10pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sys-label {
|
.sys-label {
|
||||||
font-weight: bolder;
|
font-size: 0.72em;
|
||||||
color: $base04;
|
font-weight: bold;
|
||||||
}
|
letter-spacing: 0.14em;
|
||||||
|
color: $base05;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-accent {
|
||||||
|
min-width: 3px;
|
||||||
|
border-radius: 2px;
|
||||||
|
margin-right: 8pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cpu-accent { background-color: $base0C; }
|
||||||
|
.gpu-accent { background-color: $base0E; }
|
||||||
|
.ram-accent { background-color: $base08; }
|
||||||
|
.bat-accent { background-color: $base0B; }
|
||||||
|
|
||||||
|
.section-sep {
|
||||||
|
background-color: $base02;
|
||||||
|
min-height: 2px;
|
||||||
|
margin: 6pt 0 10pt 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sys-sublabel {
|
||||||
|
font-size: 0.72em;
|
||||||
|
color: $base04;
|
||||||
|
margin-right: 8pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CPU grid
|
||||||
|
.cpu-usage-ring {
|
||||||
|
color: $base0C;
|
||||||
|
background-color: $bg0;
|
||||||
|
margin: 3pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inner freq ring — margin shrinks it inside overlay for concentric effect
|
||||||
|
// 0% = cpu min freq, 100% = cpu max freq
|
||||||
|
.cpu-freq-ring {
|
||||||
|
color: $base0D;
|
||||||
|
background-color: $bg0;
|
||||||
|
margin: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cpu-core-label {
|
||||||
|
font-size: 0.7em;
|
||||||
|
color: $base05;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GPU rings
|
||||||
|
.gpu-ring {
|
||||||
|
color: $base0E;
|
||||||
|
background-color: $bg0;
|
||||||
|
margin: 3pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gpu-freq-ring {
|
||||||
|
color: $base0D;
|
||||||
|
background-color: $bg0;
|
||||||
|
margin: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gpu-ring-value {
|
||||||
|
font-size: 0.82em;
|
||||||
|
font-weight: bold;
|
||||||
|
color: $base05;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gpu-ring-label {
|
||||||
|
font-size: 0.62em;
|
||||||
|
color: $base04;
|
||||||
|
margin-top: 2pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GPU stats row
|
||||||
|
.gpu-stats-row {
|
||||||
|
margin-top: 6pt;
|
||||||
|
margin-bottom: 2pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gpu-stat-value {
|
||||||
|
font-size: 0.85em;
|
||||||
|
font-weight: bold;
|
||||||
|
color: $base05;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gpu-stat-label {
|
||||||
|
font-size: 0.62em;
|
||||||
|
color: $base04;
|
||||||
|
}
|
||||||
|
|
||||||
|
// VRAM bar
|
||||||
|
.vram-row {
|
||||||
|
margin-top: 6pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vram-bar {
|
||||||
|
background-color: $bg0;
|
||||||
|
border-radius: $border-radius;
|
||||||
|
padding: 4pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vram-bar trough * {
|
||||||
|
background-color: $base0E;
|
||||||
|
border-radius: $border-radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vram-usage-label {
|
||||||
|
font-size: 0.62em;
|
||||||
|
color: $base04;
|
||||||
|
margin-top: 2pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// RAM ring
|
||||||
|
.ram-ring {
|
||||||
|
color: $base08;
|
||||||
|
background-color: $bg0;
|
||||||
|
margin: 4pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ram-cached-ring {
|
||||||
|
color: $base02;
|
||||||
|
background-color: transparent;
|
||||||
|
margin: 4pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ram-used-label {
|
||||||
|
font-size: 0.95em;
|
||||||
|
font-weight: bold;
|
||||||
|
color: $base05;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ram-total-label {
|
||||||
|
font-size: 0.72em;
|
||||||
|
color: $base04;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap ring
|
||||||
|
.swap-ring {
|
||||||
|
color: $base09;
|
||||||
|
background-color: $bg0;
|
||||||
|
margin: 3pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swap-section-label {
|
||||||
|
font-size: 0.62em;
|
||||||
|
color: $base04;
|
||||||
|
margin-top: 2pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Battery ring
|
||||||
|
.bat-ring {
|
||||||
|
background-color: $bg0;
|
||||||
|
margin: 4pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bat-ring-label {
|
||||||
|
font-size: 0.7em;
|
||||||
|
color: $base05;
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
(include "windows/calendar.yuck")
|
(include "windows/calendar.yuck")
|
||||||
(include "windows/sys.yuck")
|
(include "windows/sys.yuck")
|
||||||
|
(include "windows/net.yuck")
|
||||||
(include "windows/radio.yuck")
|
(include "windows/radio.yuck")
|
||||||
(include "windows/powermenu.yuck")
|
(include "windows/powermenu.yuck")
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,25 @@
|
|||||||
(deflisten net :initial '{"name":"","icon":""}'"scripts/net/net")
|
(deflisten net
|
||||||
|
:initial '{"wifi":{"connected":false,"icon":"","ssid":""},"ethernet":{"connected":false}}'
|
||||||
|
"scripts/net/net")
|
||||||
|
|
||||||
|
(deflisten bluetooth
|
||||||
|
:initial '{"powered":false,"connected":false,"device":""}'
|
||||||
|
"scripts/net/bluetooth")
|
||||||
|
|
||||||
(defwidget net-mod []
|
(defwidget net-mod []
|
||||||
(module
|
(module
|
||||||
(box
|
(eventbox
|
||||||
:orientation "v"
|
:onclick "(sleep 0.1 && eww-open-on-current-screen net --toggle)"
|
||||||
(button
|
(box :orientation "v"
|
||||||
:class "net"
|
(label
|
||||||
:tooltip {net.name}
|
:class "net-icon ${net.ethernet.connected ? 'net-active' : 'net-dim'}"
|
||||||
{net.icon})
|
:tooltip {net.ethernet.connected ? "Ethernet: Connected" : "Ethernet: Disconnected"}
|
||||||
|
:text "")
|
||||||
(button
|
(label
|
||||||
:class "blt"
|
:class "net-icon ${net.wifi.connected ? 'net-active' : 'net-dim'}"
|
||||||
(label :class "icon-text" :text "B"))
|
:tooltip {net.wifi.connected ? "WiFi: ${net.wifi.ssid}" : "WiFi: Disconnected"}
|
||||||
)
|
:text {net.wifi.icon})
|
||||||
)
|
(label
|
||||||
)
|
:class "net-icon ${bluetooth.connected ? 'blt-connected' : bluetooth.powered ? 'blt-on' : 'net-dim'}"
|
||||||
|
:tooltip {bluetooth.connected ? "Bluetooth: ${bluetooth.device}" : bluetooth.powered ? "Bluetooth: On" : "Bluetooth: Off"}
|
||||||
|
:text {bluetooth.connected ? "" : bluetooth.powered ? "" : ""})))))
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
(deflisten cpu :initial '{}' "scripts/sys/cpu")
|
(deflisten cpu :initial '{}' "scripts/sys/cpu")
|
||||||
(deflisten gpu :initial '{"devices":[{"GRBM2":{}}]}' "scripts/sys/gpu")
|
(deflisten gpu :initial '{"gfx_pct":0,"mem_pct":0,"media_pct":0,"sclk":0,"mclk":0,"sclk_pct":0,"mclk_pct":0,"vclk":0,"vclk_pct":0,"temp":0,"power":0,"vram_used":0,"vram_total":1}' "scripts/sys/gpu")
|
||||||
(deflisten memory :initial '{"human":{"used":"0G","total":"0G"},"used":0.0,"total":1.0}' "scripts/sys/memory")
|
(deflisten memory :initial '{"human":{"used":"0G","total":"0G","cached":"0G"},"used":0.0,"total":1.0,"cached":0.0}' "scripts/sys/memory")
|
||||||
|
|
||||||
(deflisten battery :initial '{"visible":false,"percentage":0.0,"color":"#FFFFFF"}' "scripts/sys/battery")
|
(deflisten battery :initial '{"visible":false,"percentage":0.0,"color":"#FFFFFF"}' "scripts/sys/battery")
|
||||||
|
|
||||||
@@ -8,35 +8,26 @@
|
|||||||
(module
|
(module
|
||||||
(eventbox
|
(eventbox
|
||||||
:onclick "(sleep 0.1 && eww-open-on-current-screen sys --toggle)"
|
:onclick "(sleep 0.1 && eww-open-on-current-screen sys --toggle)"
|
||||||
(box
|
(box :orientation "v"
|
||||||
:orientation "v"
|
(circular-progress
|
||||||
(circular-progress
|
:value {EWW_CPU.avg}
|
||||||
:value {EWW_CPU.avg}
|
:class "cpubar"
|
||||||
:class "cpubar"
|
:width 28 :height 28 :thickness 6
|
||||||
:thickness 6
|
:tooltip "CPU ${round(EWW_CPU.avg, 0)}%")
|
||||||
(label :class "icon-text" :text "C"))
|
(circular-progress
|
||||||
|
:value {gpu.gfx_pct}
|
||||||
(circular-progress
|
:class "gpubar"
|
||||||
:value {gpu.devices[0].GRBM2?.CommandProcessor-Graphics?.value?:0}
|
:width 28 :height 28 :thickness 6
|
||||||
:class "gpubar"
|
:tooltip "GPU ${round(gpu.gfx_pct, 0)}%")
|
||||||
:thickness 6
|
|
||||||
(label :class "icon-text" :text "G"))
|
|
||||||
(circular-progress
|
(circular-progress
|
||||||
:value {100*memory.used/memory.total}
|
:value {100*memory.used/memory.total}
|
||||||
:class "membar"
|
:class "membar"
|
||||||
:thickness 6
|
:width 28 :height 28 :thickness 6
|
||||||
:tooltip "${memory.human.used} / ${memory.human.total}"
|
:tooltip "RAM ${memory.human.used} / ${memory.human.total}")
|
||||||
(label :class "icon-text" :text "M"))
|
|
||||||
|
|
||||||
(circular-progress
|
(circular-progress
|
||||||
:value {battery.percentage}
|
:value {battery.percentage}
|
||||||
:class "batbar"
|
:class "batbar"
|
||||||
:visible {battery.visible}
|
:visible {battery.visible}
|
||||||
:style "color: ${battery.color};"
|
:style "color: ${battery.color};"
|
||||||
:thickness 6
|
:width 28 :height 28 :thickness 6
|
||||||
:tooltip "${battery.status} @ ${battery.wattage}"
|
:tooltip "Bat ${round(battery.percentage, 0)}% · ${battery.status} @ ${battery.wattage}")))))
|
||||||
(label :class "icon-text" :text "B"))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
emit() {
|
||||||
|
local powered=false connected=false device=""
|
||||||
|
|
||||||
|
if bluetoothctl show 2>/dev/null | grep -q "Powered: yes"; then
|
||||||
|
powered=true
|
||||||
|
while IFS= read -r line; do
|
||||||
|
mac=$(echo "$line" | awk '{ print $2 }')
|
||||||
|
info=$(bluetoothctl info "$mac" 2>/dev/null)
|
||||||
|
if echo "$info" | grep -q "Connected: yes"; then
|
||||||
|
device=$(echo "$info" | awk -F': ' '/^\tName:/ { print $2; exit }')
|
||||||
|
connected=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done < <(bluetoothctl devices 2>/dev/null)
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '{"powered":%s,"connected":%s,"device":"%s"}\n' "$powered" "$connected" "$device"
|
||||||
|
}
|
||||||
|
|
||||||
|
emit
|
||||||
|
bluetoothctl monitor 2>/dev/null | while IFS= read -r line; do
|
||||||
|
case "$line" in
|
||||||
|
*"Powered"*|*"Connected"*|*"Device"*) emit ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
emit() {
|
||||||
|
local first=true arr=""
|
||||||
|
while IFS= read -r line; do
|
||||||
|
local mac name connected
|
||||||
|
mac=$(echo "$line" | awk '{ print $2 }')
|
||||||
|
name=$(echo "$line" | awk '{ $1=$2=""; sub(/^ +/, ""); print }')
|
||||||
|
info=$(bluetoothctl info "$mac" 2>/dev/null)
|
||||||
|
connected=$(echo "$info" | grep -q "Connected: yes" && echo true || echo false)
|
||||||
|
$first || arr="${arr},"
|
||||||
|
arr="${arr}{\"mac\":\"${mac}\",\"name\":\"${name}\",\"connected\":${connected}}"
|
||||||
|
first=false
|
||||||
|
done < <(bluetoothctl devices 2>/dev/null)
|
||||||
|
echo "[${arr}]"
|
||||||
|
}
|
||||||
|
|
||||||
|
emit
|
||||||
|
bluetoothctl monitor 2>/dev/null | while IFS= read -r line; do
|
||||||
|
case "$line" in
|
||||||
|
*"Powered"*|*"Connected"*|*"Device"*) emit ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
mac="$1"
|
||||||
|
if bluetoothctl info "$mac" 2>/dev/null | grep -q "Connected: yes"; then
|
||||||
|
bluetoothctl disconnect "$mac"
|
||||||
|
else
|
||||||
|
bluetoothctl connect "$mac"
|
||||||
|
fi
|
||||||
@@ -1,81 +1,47 @@
|
|||||||
#!/usr/bin/env zsh
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
function get_time_ms {
|
get_wifi_iface() {
|
||||||
date -u +%s%3N
|
|
||||||
}
|
|
||||||
|
|
||||||
icons=("" "" "" "" "")
|
|
||||||
|
|
||||||
function get_wifi_interface() {
|
|
||||||
awk 'NR > 2 { gsub(":", "", $1); print $1; exit }' /proc/net/wireless
|
awk 'NR > 2 { gsub(":", "", $1); print $1; exit }' /proc/net/wireless
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggle() {
|
signal_icon() {
|
||||||
status=$(rfkill | grep wlan | awk '{print $4}')
|
local dbm="$1"
|
||||||
|
if [ -z "$dbm" ]; then echo ""; return; fi
|
||||||
if [ "$status" = "unblocked" ]; then
|
if [ "$dbm" -ge -50 ]; then echo ""
|
||||||
rfkill block wlan
|
elif [ "$dbm" -ge -60 ]; then echo ""
|
||||||
else
|
elif [ "$dbm" -ge -70 ]; then echo ""
|
||||||
rfkill unblock wlan
|
elif [ "$dbm" -ge -80 ]; then echo ""
|
||||||
fi
|
else echo ""; fi
|
||||||
}
|
}
|
||||||
|
|
||||||
function gen_wifi() {
|
make_content() {
|
||||||
wifi_iface=$(get_wifi_interface)
|
local wifi_iface eth_iface
|
||||||
signal=$(awk -v iface="$wifi_iface" '$1 == iface ":" { print $3; exit }' /proc/net/wireless)
|
|
||||||
level=$(awk -v n="$signal" 'BEGIN{print int((n-1)/20)}')
|
wifi_iface=$(get_wifi_iface)
|
||||||
if [ "$level" -gt 4 ]; then
|
eth_iface=$(ip link | awk '/^[0-9]+: en[po]/ { gsub(":",""); print $2; exit }')
|
||||||
level=4
|
|
||||||
|
# Ethernet
|
||||||
|
local eth_connected=false
|
||||||
|
if [ -n "$eth_iface" ]; then
|
||||||
|
eth_state=$(ip link show "$eth_iface" 2>/dev/null | awk '/state/ { print $9 }')
|
||||||
|
[ "$eth_state" = "UP" ] && eth_connected=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
icon=${icons[$level]}
|
# WiFi — use IP presence as connection indicator (more reliable than wpa_cli)
|
||||||
ip="-"
|
local wifi_connected=false wifi_icon="" wifi_ssid=""
|
||||||
class="net-connected"
|
if [ -n "$wifi_iface" ] && ip -4 addr show "$wifi_iface" 2>/dev/null | grep -q "inet "; then
|
||||||
name_raw=$(wpa_cli -g "/run/wpa_supplicant/$wifi_iface" status | grep \^ssid= | sed 's/ssid=//g')
|
wifi_connected=true
|
||||||
name=$(printf "%s" "$name_raw")
|
wifi_ssid=$(wpa_cli -g "/run/wpa_supplicant/$wifi_iface" status 2>/dev/null \
|
||||||
}
|
| awk -F= '/^ssid=/ { print $2 }')
|
||||||
|
signal=$(awk -v iface="$wifi_iface" '$1 == iface ":" { gsub(/\./, "", $4); print $4; exit }' /proc/net/wireless)
|
||||||
function gen_ethernet() {
|
wifi_icon=$(signal_icon "$signal")
|
||||||
icon=""
|
|
||||||
class="net-connected"
|
|
||||||
ip=""
|
|
||||||
name=Wired
|
|
||||||
}
|
|
||||||
|
|
||||||
function make_content() {
|
|
||||||
local ethernet wifi wifi_iface
|
|
||||||
ethernet=$(ip link | rg "^[0-9]+: en[po]+" | head -n1 | sed 's/[a-zA-Z0-9_,><:\ -]*state //g' | sed 's/ mode [a-zA-Z0-9 ]*//g')
|
|
||||||
wifi_iface=$(get_wifi_interface)
|
|
||||||
if [ -n "$wifi_iface" ]; then
|
|
||||||
wifi=$(wpa_cli -g "/run/wpa_supplicant/$wifi_iface" status | rg "^wpa_state=" | sed 's/wpa_state=//g')
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# test ethernet first
|
printf '{"wifi":{"connected":%s,"icon":"%s","ssid":"%s"},"ethernet":{"connected":%s}}\n' \
|
||||||
if [[ $ethernet == "UP" ]]; then
|
"$wifi_connected" "$wifi_icon" "$wifi_ssid" "$eth_connected"
|
||||||
gen_ethernet
|
|
||||||
elif [[ $wifi == "COMPLETED" ]]; then
|
|
||||||
gen_wifi
|
|
||||||
else
|
|
||||||
icon=""
|
|
||||||
ip="-"
|
|
||||||
class="net-disconnected"
|
|
||||||
name="Disconnected"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo '{"icon": "'$icon'", "name": "'$name'", "ip": "'$ip'", "class": "'$class'"}'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if [ "$1" = "toggle" ]; then
|
make_content
|
||||||
toggle
|
ip monitor | while read -r _; do
|
||||||
else
|
|
||||||
last_time=$(get_time_ms)
|
|
||||||
make_content
|
make_content
|
||||||
ip monitor | while read -r _; do
|
done
|
||||||
current_time=$(get_time_ms)
|
|
||||||
delta=$((current_time - last_time))
|
|
||||||
if [[ $delta -gt 50 ]]; then
|
|
||||||
make_content
|
|
||||||
last_time=$(get_time_ms)
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|||||||
+64
@@ -0,0 +1,64 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
get_wifi_iface() {
|
||||||
|
awk 'NR > 2 { gsub(":", "", $1); print $1; exit }' /proc/net/wireless
|
||||||
|
}
|
||||||
|
|
||||||
|
freq_band() {
|
||||||
|
local f="$1"
|
||||||
|
if [ "$f" -ge 6000 ] 2>/dev/null; then echo "6 GHz"
|
||||||
|
elif [ "$f" -ge 5000 ] 2>/dev/null; then echo "5 GHz"
|
||||||
|
elif [ "$f" -ge 2400 ] 2>/dev/null; then echo "2.4 GHz"
|
||||||
|
else echo ""; fi
|
||||||
|
}
|
||||||
|
|
||||||
|
wifi_gen_label() {
|
||||||
|
case "$1" in
|
||||||
|
7) echo "Wi-Fi 7" ;; 6) echo "Wi-Fi 6" ;;
|
||||||
|
5) echo "Wi-Fi 5" ;; 4) echo "Wi-Fi 4" ;;
|
||||||
|
*) echo "" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
make_content() {
|
||||||
|
local wifi_iface eth_iface
|
||||||
|
wifi_iface=$(get_wifi_iface)
|
||||||
|
eth_iface=$(ip link | awk '/^[0-9]+: en[po]/ { gsub(":",""); print $2; exit }')
|
||||||
|
|
||||||
|
local wifi_ssid="" wifi_ip="" wifi_freq=0 wifi_band="" wifi_gen="" wifi_signal=0
|
||||||
|
if [ -n "$wifi_iface" ] && ip -4 addr show "$wifi_iface" 2>/dev/null | grep -q "inet "; then
|
||||||
|
local wpa
|
||||||
|
wpa=$(wpa_cli -g "/run/wpa_supplicant/$wifi_iface" status 2>/dev/null)
|
||||||
|
wifi_ssid=$(echo "$wpa" | awk -F= '/^ssid=/ { print $2 }')
|
||||||
|
wifi_ip=$(ip -4 addr show "$wifi_iface" | awk '/inet / { print $2 }')
|
||||||
|
wifi_freq=$(echo "$wpa" | awk -F= '/^freq=/ { print $2 }')
|
||||||
|
local gen
|
||||||
|
gen=$(echo "$wpa" | awk -F= '/^wifi_generation=/ { print $2 }')
|
||||||
|
wifi_band=$(freq_band "$wifi_freq")
|
||||||
|
wifi_gen=$(wifi_gen_label "$gen")
|
||||||
|
wifi_signal=$(awk -v iface="$wifi_iface" \
|
||||||
|
'$1 == iface ":" { gsub(/\./, "", $4); print $4; exit }' /proc/net/wireless)
|
||||||
|
fi
|
||||||
|
|
||||||
|
local eth_ip="" eth_speed="" eth_state="down"
|
||||||
|
if [ -n "$eth_iface" ]; then
|
||||||
|
eth_state=$(ip link show "$eth_iface" 2>/dev/null | awk '/state/ { print tolower($9) }')
|
||||||
|
if [ "$eth_state" = "up" ]; then
|
||||||
|
eth_ip=$(ip -4 addr show "$eth_iface" | awk '/inet / { print $2 }')
|
||||||
|
local spd
|
||||||
|
spd=$(cat /sys/class/net/"$eth_iface"/speed 2>/dev/null)
|
||||||
|
[ "${spd:-0}" -gt 0 ] 2>/dev/null && eth_speed="${spd} Mbps"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# shellcheck disable=SC2059
|
||||||
|
printf '{"wifi":{"ssid":"%s","ip":"%s","freq":%s,"band":"%s","gen":"%s","signal":%s},' \
|
||||||
|
"$wifi_ssid" "$wifi_ip" "${wifi_freq:-0}" "$wifi_band" "$wifi_gen" "${wifi_signal:-0}"
|
||||||
|
printf '"ethernet":{"state":"%s","ip":"%s","speed":"%s","interface":"%s"}}\n' \
|
||||||
|
"$eth_state" "$eth_ip" "$eth_speed" "${eth_iface:-}"
|
||||||
|
}
|
||||||
|
|
||||||
|
make_content
|
||||||
|
ip monitor | while read -r _; do
|
||||||
|
make_content
|
||||||
|
done
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
COLS=4
|
||||||
|
declare -A prev_idle prev_total
|
||||||
|
|
||||||
|
emit_grid() {
|
||||||
|
local rows=() row_items=()
|
||||||
|
while IFS= read -r line; do
|
||||||
|
[[ $line =~ ^cpu([0-9]+) ]] || continue
|
||||||
|
local core="${BASH_REMATCH[1]}"
|
||||||
|
read -ra f <<< "$line"
|
||||||
|
local idle=$(( f[4] + f[5] ))
|
||||||
|
local total=0
|
||||||
|
for x in "${f[@]:1}"; do (( total += x )); done
|
||||||
|
|
||||||
|
local usage="0.0"
|
||||||
|
if [[ -n "${prev_total[$core]+x}" ]]; then
|
||||||
|
local dt=$(( total - prev_total[$core] ))
|
||||||
|
local di=$(( idle - prev_idle[$core] ))
|
||||||
|
(( dt > 0 )) && usage=$(awk "BEGIN{printf \"%.1f\", 100*(1-$di/$dt)}")
|
||||||
|
fi
|
||||||
|
prev_idle[$core]=$idle
|
||||||
|
prev_total[$core]=$total
|
||||||
|
|
||||||
|
local freq=0
|
||||||
|
local fpath="/sys/devices/system/cpu/cpu${core}/cpufreq/scaling_cur_freq"
|
||||||
|
[[ -r $fpath ]] && freq=$(( $(< "$fpath") / 1000 ))
|
||||||
|
|
||||||
|
row_items+=("{\"core\":$core,\"usage\":$usage,\"freq\":$freq}")
|
||||||
|
|
||||||
|
if (( ${#row_items[@]} == COLS )); then
|
||||||
|
local row; printf -v row '%s,' "${row_items[@]}"; row="${row%,}"
|
||||||
|
rows+=("[$row]")
|
||||||
|
row_items=()
|
||||||
|
fi
|
||||||
|
done < /proc/stat
|
||||||
|
|
||||||
|
if (( ${#row_items[@]} > 0 )); then
|
||||||
|
local row; printf -v row '%s,' "${row_items[@]}"; row="${row%,}"
|
||||||
|
rows+=("[$row]")
|
||||||
|
fi
|
||||||
|
|
||||||
|
local out; printf -v out '%s,' "${rows[@]}"; out="${out%,}"
|
||||||
|
echo "[$out]"
|
||||||
|
}
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
emit_grid
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
dir=$(grep -rl k10temp /sys/class/hwmon/*/name 2>/dev/null | head -1 | xargs dirname)
|
||||||
|
awk '{printf "%.0f\n", $1/1000}' "$dir/temp1_input"
|
||||||
@@ -1,3 +1,22 @@
|
|||||||
#!/usr/bin/env zsh
|
#!/usr/bin/env bash
|
||||||
|
amdgpu_top -J -s 5000 | jq -c --unbuffered '.devices[0] | {
|
||||||
amdgpu_top -J -s 5000 | sed 's/ //g'
|
gfx_pct: (.gpu_activity.GFX.value // 0),
|
||||||
|
mem_pct: (.gpu_activity.Memory.value // 0),
|
||||||
|
media_pct: (.gpu_activity.MediaEngine.value // 0),
|
||||||
|
sclk: (.Sensors.GFX_SCLK.value // 0),
|
||||||
|
mclk: (.Sensors.GFX_MCLK.value // 0),
|
||||||
|
sclk_pct: (if (.Info["GPU Clock"].max != .Info["GPU Clock"].min) then
|
||||||
|
100 * ((.Sensors.GFX_SCLK.value // 0) - .Info["GPU Clock"].min) / (.Info["GPU Clock"].max - .Info["GPU Clock"].min)
|
||||||
|
else 0 end),
|
||||||
|
mclk_pct: (if (.Info["Memory Clock"].max != .Info["Memory Clock"].min) then
|
||||||
|
100 * ((.Sensors.GFX_MCLK.value // 0) - .Info["Memory Clock"].min) / (.Info["Memory Clock"].max - .Info["Memory Clock"].min)
|
||||||
|
else 0 end),
|
||||||
|
vclk: (.gpu_metrics.average_vclk_frequency // 0),
|
||||||
|
vclk_pct: (if (.Info["GPU Clock"].max > 0) then
|
||||||
|
100 * (.gpu_metrics.average_vclk_frequency // 0) / .Info["GPU Clock"].max
|
||||||
|
else 0 end),
|
||||||
|
temp: (.Sensors["Edge Temperature"].value // 0),
|
||||||
|
power: (.Sensors["Average Power"].value // 0),
|
||||||
|
vram_used: (.VRAM["Total VRAM Usage"].value // 0),
|
||||||
|
vram_total: (.VRAM["Total VRAM"].value // 1)
|
||||||
|
}'
|
||||||
|
|||||||
@@ -8,5 +8,6 @@ human() {
|
|||||||
|
|
||||||
free --si -s 3 | rg --line-buffered Mem | while read -r line; do
|
free --si -s 3 | rg --line-buffered Mem | while read -r line; do
|
||||||
used=$(echo "$line" | awk '{print $3}')
|
used=$(echo "$line" | awk '{print $3}')
|
||||||
echo '{"human": { "total": "'$(human "$total")'", "used": "'$(human "$used")'"}, "total": "'$total'" , "used": "'$used'"}'
|
cached=$(echo "$line" | awk '{print $6}')
|
||||||
done
|
echo '{"human": {"total": "'$(human "$total")'", "used": "'$(human "$used")'", "cached": "'$(human "$cached")'"}, "total": "'$total'", "used": "'$used'", "cached": "'$cached'"}'
|
||||||
|
done
|
||||||
|
|||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
free --si | awk '/Swap/ {
|
||||||
|
used=$3; total=$2
|
||||||
|
u = sprintf("%.1fG", used/1000000)
|
||||||
|
t = sprintf("%.1fG", total/1000000)
|
||||||
|
printf "{\"used\":%d,\"total\":%d,\"human\":{\"used\":\"%s\",\"total\":\"%s\"}}\n", used, total, u, t
|
||||||
|
}'
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
|
||||||
|
(defpoll netinfo
|
||||||
|
:interval "5s"
|
||||||
|
:initial '{"wifi":{"ssid":"","ip":"","freq":0,"band":"","gen":"","signal":0},"ethernet":{"state":"down","ip":"","speed":"","interface":""}}'
|
||||||
|
"scripts/net/netinfo")
|
||||||
|
|
||||||
|
(deflisten bt-devices
|
||||||
|
:initial "[]"
|
||||||
|
"scripts/net/bt-devices")
|
||||||
|
|
||||||
|
; --- Shared row widget ---
|
||||||
|
|
||||||
|
(defwidget netinfo-row [label value]
|
||||||
|
(box :orientation "h" :space-evenly false :class "netinfo-row"
|
||||||
|
(label :class "netinfo-label" :halign "start" :text label)
|
||||||
|
(label :class "netinfo-value" :halign "end" :hexpand true :text value)))
|
||||||
|
|
||||||
|
; --- WiFi ---
|
||||||
|
|
||||||
|
(defwidget wifi-net-section []
|
||||||
|
(box :orientation "v" :space-evenly false :class "sys-section"
|
||||||
|
(section-header :title "WiFi" :accent "wifi-accent")
|
||||||
|
(box :orientation "v" :space-evenly false
|
||||||
|
:visible {net.wifi.connected}
|
||||||
|
(netinfo-row :label "SSID" :value {netinfo.wifi.ssid})
|
||||||
|
(netinfo-row :label "IP" :value {netinfo.wifi.ip})
|
||||||
|
(netinfo-row :label "Signal" :value "${net.wifi.icon} ${netinfo.wifi.signal} dBm")
|
||||||
|
(netinfo-row :label "Freq" :value "${netinfo.wifi.freq} MHz · ${netinfo.wifi.band} · ${netinfo.wifi.gen}"))
|
||||||
|
(label :class "netinfo-dim" :halign "start"
|
||||||
|
:visible {!net.wifi.connected}
|
||||||
|
:text "Not connected")))
|
||||||
|
|
||||||
|
; --- Ethernet ---
|
||||||
|
|
||||||
|
(defwidget ethernet-net-section []
|
||||||
|
(box :orientation "v" :space-evenly false :class "sys-section"
|
||||||
|
(section-header :title "Ethernet" :accent "eth-accent")
|
||||||
|
(box :orientation "v" :space-evenly false
|
||||||
|
:visible {net.ethernet.connected}
|
||||||
|
(netinfo-row :label "Interface" :value {netinfo.ethernet.interface})
|
||||||
|
(netinfo-row :label "IP" :value {netinfo.ethernet.ip})
|
||||||
|
(netinfo-row :label "Speed" :value {netinfo.ethernet.speed}))
|
||||||
|
(label :class "netinfo-dim" :halign "start"
|
||||||
|
:visible {!net.ethernet.connected}
|
||||||
|
:text "No carrier")))
|
||||||
|
|
||||||
|
; --- Bluetooth ---
|
||||||
|
|
||||||
|
(defwidget bt-device-row [device]
|
||||||
|
(box :orientation "h" :space-evenly false :class "bt-device-row" :valign "center"
|
||||||
|
(label :class "bt-device-name" :hexpand true :halign "start" :text {device.name})
|
||||||
|
(button
|
||||||
|
:class "bt-device-btn ${device.connected ? 'bt-btn-on' : 'bt-btn-off'}"
|
||||||
|
:onclick "scripts/net/bt-toggle ${device.mac}"
|
||||||
|
:tooltip {device.connected ? "Disconnect" : "Connect"}
|
||||||
|
(label :text {device.connected ? "" : ""}))))
|
||||||
|
|
||||||
|
(defwidget bluetooth-net-section []
|
||||||
|
(box :orientation "v" :space-evenly false :class "sys-section"
|
||||||
|
(section-header :title "Bluetooth" :accent "blt-accent")
|
||||||
|
(box :orientation "v" :space-evenly false
|
||||||
|
(for device in {bt-devices}
|
||||||
|
(bt-device-row :device {device})))))
|
||||||
|
|
||||||
|
; --- Root ---
|
||||||
|
|
||||||
|
(defwidget net-win []
|
||||||
|
(box :class "sys-win" :space-evenly false :orientation "v"
|
||||||
|
(wifi-net-section)
|
||||||
|
(box :class "section-sep")
|
||||||
|
(ethernet-net-section)
|
||||||
|
(box :class "section-sep")
|
||||||
|
(bluetooth-net-section)))
|
||||||
|
|
||||||
|
(defwindow net
|
||||||
|
:monitor 0
|
||||||
|
:stacking "overlay"
|
||||||
|
:geometry (geometry
|
||||||
|
:x "0%" :y "0%"
|
||||||
|
:anchor "bottom right"
|
||||||
|
:width "300px" :height "0px")
|
||||||
|
(window (net-win)))
|
||||||
@@ -1,130 +1,212 @@
|
|||||||
|
|
||||||
|
(defpoll swap :interval "5s" "scripts/sys/swap")
|
||||||
|
|
||||||
|
(defpoll cpu-freq-min :interval "60s"
|
||||||
|
"awk '{print $1/1000}' /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq")
|
||||||
|
(defpoll cpu-freq-max :interval "60s"
|
||||||
|
"awk '{print $1/1000}' /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq")
|
||||||
|
(defpoll cpu-temp :interval "2s"
|
||||||
|
"scripts/sys/cputemp")
|
||||||
|
(defpoll cpu-freq-avg :interval "2s"
|
||||||
|
"awk '{sum+=$1; count++} END {printf \"%.1f\", sum/count/1000000}' /sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq")
|
||||||
|
|
||||||
|
(deflisten cpu-grid :initial '[[{"core":0,"usage":0,"freq":0}]]'
|
||||||
|
"bash scripts/sys/cpugrid")
|
||||||
|
|
||||||
|
; --- Shared ---
|
||||||
|
|
||||||
|
(defwidget section-header [title accent]
|
||||||
|
(box :orientation "h" :space-evenly false :valign "center" :class "sys-section-header"
|
||||||
|
(box :class "section-accent ${accent}")
|
||||||
|
(label :class "sys-label" :text title)))
|
||||||
|
|
||||||
|
; --- CPU ---
|
||||||
|
|
||||||
|
(defwidget cpu-core-cell [core]
|
||||||
|
(overlay
|
||||||
|
(circular-progress
|
||||||
|
:width 60 :height 60
|
||||||
|
:value {core.usage}
|
||||||
|
:start-at 0
|
||||||
|
:clockwise true
|
||||||
|
:thickness 7
|
||||||
|
:class "cpu-usage-ring")
|
||||||
|
(circular-progress
|
||||||
|
:value {100 * (core.freq - cpu-freq-min) / (cpu-freq-max - cpu-freq-min)}
|
||||||
|
:start-at 0
|
||||||
|
:clockwise true
|
||||||
|
:thickness 4
|
||||||
|
:class "cpu-freq-ring")
|
||||||
|
(box :halign "center" :valign "center"
|
||||||
|
(label :class "cpu-core-label" :text "${core.core}"))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
(defwidget cpu-sys-win []
|
(defwidget cpu-sys-win []
|
||||||
(box
|
(box :orientation "v" :space-evenly false :class "sys-section"
|
||||||
:orientation "v"
|
(section-header :title "CPU" :accent "cpu-accent")
|
||||||
:space-evenly false
|
(box :orientation "v" :space-evenly false :halign "center"
|
||||||
(box :class "sys-label" "CPU")
|
(for row in {cpu-grid}
|
||||||
(box
|
(box :orientation "h" :space-evenly false
|
||||||
:orientation "v"
|
(for core in {row}
|
||||||
(for core in {EWW_CPU.cores}
|
(cpu-core-cell :core {core})))))
|
||||||
(box
|
(box :orientation "h" :space-evenly true :class "gpu-stats-row"
|
||||||
:space-evenly false
|
(box :orientation "v" :halign "center" :space-evenly false
|
||||||
:class "cpu-core ${core.core}"
|
(label :class "gpu-stat-value" :text "${cpu-temp}°C")
|
||||||
(progress
|
(label :class "gpu-stat-label" :text "temp"))
|
||||||
:value {core.usage}
|
(box :orientation "v" :halign "center" :space-evenly false
|
||||||
:orientation "h"
|
(label :class "gpu-stat-value" :text "${cpu-freq-avg}GHz")
|
||||||
:flipped true
|
(label :class "gpu-stat-label" :text "avg clk"))
|
||||||
:class "cpu-core-usage"
|
(box :orientation "v" :halign "center" :space-evenly false
|
||||||
:tooltip "${core.core} @ ${core.freq}Mhz"
|
(label :class "gpu-stat-value" :text "${round(EWW_CPU.avg, 0)}%")
|
||||||
)
|
(label :class "gpu-stat-label" :text "usage")))
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
; --- GPU ---
|
||||||
|
|
||||||
(defwidget gpu-sys-win []
|
(defwidget gpu-sys-win []
|
||||||
(box
|
(box :orientation "v" :space-evenly false :class "sys-section"
|
||||||
:orientation "v"
|
(section-header :title "GPU" :accent "gpu-accent")
|
||||||
:space-evenly false
|
(box :orientation "h" :space-evenly true
|
||||||
(box :class "sys-label" "GPU")
|
; GFX — outer: activity%, inner: clock% of range
|
||||||
(progress
|
(box :orientation "v" :space-evenly false :halign "center"
|
||||||
:value {gpu.devices[0].GRBM2?.CommandProcessor-Compute?.value?:0.0}
|
(overlay
|
||||||
:orientation "h"
|
(circular-progress :width 68 :height 68
|
||||||
:flipped true
|
:value {gpu.gfx_pct}
|
||||||
:class "gpu-core-usage"
|
:start-at 0 :clockwise true :thickness 7
|
||||||
:tooltip "Compute"
|
:class "gpu-ring"
|
||||||
)
|
:tooltip "GFX ${round(gpu.gfx_pct, 1)}%\nClock ${gpu.sclk} MHz (${round(gpu.sclk_pct, 0)}% of range)")
|
||||||
(progress
|
(circular-progress
|
||||||
:value {gpu.devices[0].GRBM2?.CommandProcessor-Fetcher?.value?:0.0}
|
:value {gpu.sclk_pct}
|
||||||
:orientation "h"
|
:start-at 0 :clockwise true :thickness 4
|
||||||
:flipped true
|
:class "gpu-freq-ring")
|
||||||
:class "gpu-core-usage"
|
(box :halign "center" :valign "center"
|
||||||
:tooltip "Fetcher"
|
(label :class "gpu-ring-value" :text "${round(gpu.gfx_pct, 0)}%")))
|
||||||
)
|
(label :class "gpu-ring-label" :text "GFX" :halign "center"))
|
||||||
(progress
|
; Memory — outer: activity%, inner: memory clock% of range
|
||||||
:value {gpu.devices[0].GRBM2?.CommandProcessor-Graphics?.value?:0.0}
|
(box :orientation "v" :space-evenly false :halign "center"
|
||||||
:orientation "h"
|
(overlay
|
||||||
:flipped true
|
(circular-progress :width 68 :height 68
|
||||||
:class "gpu-core-usage"
|
:value {gpu.mem_pct}
|
||||||
:tooltip "Graphics"
|
:start-at 0 :clockwise true :thickness 7
|
||||||
)
|
:class "gpu-ring"
|
||||||
(box :class "spacer")
|
:tooltip "Memory ${round(gpu.mem_pct, 1)}%\nClock ${gpu.mclk} MHz (${round(gpu.mclk_pct, 0)}% of range)")
|
||||||
(progress
|
(circular-progress
|
||||||
:value {gpu.devices[0]?.gpu_activity?.GFX?.value?:0.0}
|
:value {gpu.mclk_pct}
|
||||||
:orientation "h"
|
:start-at 0 :clockwise true :thickness 4
|
||||||
:flipped true
|
:class "gpu-freq-ring")
|
||||||
:class "gpu-core-usage"
|
(box :halign "center" :valign "center"
|
||||||
:tooltip "GFX"
|
(label :class "gpu-ring-value" :text "${round(gpu.mem_pct, 0)}%")))
|
||||||
)
|
(label :class "gpu-ring-label" :text "Mem" :halign "center"))
|
||||||
(progress
|
; Media — outer: activity%, inner: video clock% of GPU clock max
|
||||||
:value {gpu.devices[0]?.gpu_activity?.Memory?.value?:0.0}
|
(box :orientation "v" :space-evenly false :halign "center"
|
||||||
:orientation "h"
|
(overlay
|
||||||
:flipped true
|
(circular-progress :width 68 :height 68
|
||||||
:class "gpu-core-usage"
|
:value {gpu.media_pct}
|
||||||
:tooltip "Memory"
|
:start-at 0 :clockwise true :thickness 7
|
||||||
)
|
:class "gpu-ring"
|
||||||
(progress
|
:tooltip "Media Engine ${round(gpu.media_pct, 1)}%\nVideo Clock ${gpu.vclk} MHz")
|
||||||
:value {gpu.devices[0]?.gpu_activity?.MediaEngine?.value?:0.0}
|
(circular-progress
|
||||||
:orientation "h"
|
:value {gpu.vclk_pct}
|
||||||
:flipped true
|
:start-at 0 :clockwise true :thickness 4
|
||||||
:class "gpu-core-usage"
|
:class "gpu-freq-ring")
|
||||||
:tooltip "Media"
|
(box :halign "center" :valign "center"
|
||||||
)
|
(label :class "gpu-ring-value" :text "${round(gpu.media_pct, 0)}%")))
|
||||||
(box :class "spacer")
|
(label :class "gpu-ring-label" :text "Med" :halign "center")))
|
||||||
(progress
|
(box :orientation "v" :space-evenly false :class "vram-row"
|
||||||
:value {100*(gpu.devices[0]?.VRAM?.TotalVRAMUsage?.value?:0.0)/(gpu.devices[0]?.VRAM?.TotalVRAM?.value?:1.0)}
|
(progress
|
||||||
:orientation "h"
|
:value {100 * gpu.vram_used / gpu.vram_total}
|
||||||
:flipped true
|
:orientation "h" :flipped true :hexpand true
|
||||||
:class "gpu-core-usage"
|
:class "vram-bar"
|
||||||
:tooltip "VRAM"
|
:tooltip "VRAM ${round(100 * gpu.vram_used / gpu.vram_total, 1)}%")
|
||||||
|
(box :orientation "h" :space-evenly false
|
||||||
|
(label :class "vram-usage-label" :halign "start" :hexpand true :text "VRAM")
|
||||||
|
(label :class "vram-usage-label" :halign "end"
|
||||||
|
:text "${round(gpu.vram_used / 1024, 1)} / ${round(gpu.vram_total / 1024, 1)} GiB")))
|
||||||
|
(box :orientation "h" :space-evenly true :class "gpu-stats-row"
|
||||||
|
(box :orientation "v" :halign "center" :space-evenly false
|
||||||
|
(label :class "gpu-stat-value" :text "${gpu.temp}°C")
|
||||||
|
(label :class "gpu-stat-label" :text "temp"))
|
||||||
|
(box :orientation "v" :halign "center" :space-evenly false
|
||||||
|
(label :class "gpu-stat-value" :text "${gpu.power}W")
|
||||||
|
(label :class "gpu-stat-label" :text "power")))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
; --- RAM ---
|
||||||
|
|
||||||
|
(defwidget ram-sys-win []
|
||||||
|
(box :orientation "v" :space-evenly false :class "sys-section"
|
||||||
|
(section-header :title "RAM" :accent "ram-accent")
|
||||||
|
(box :orientation "h" :space-evenly false :halign "center" :spacing 16 :valign "center"
|
||||||
|
(overlay
|
||||||
|
(circular-progress
|
||||||
|
:width 88 :height 88
|
||||||
|
:value {100*memory.used/memory.total}
|
||||||
|
:start-at 0
|
||||||
|
:clockwise true
|
||||||
|
:thickness 9
|
||||||
|
:class "ram-ring"
|
||||||
|
:tooltip "RAM\nUsed ${memory.human.used} / ${memory.human.total}\nCached ${memory.human.cached}")
|
||||||
|
(circular-progress
|
||||||
|
:value {100*memory.cached/memory.total}
|
||||||
|
:start-at {100*memory.used/memory.total}
|
||||||
|
:clockwise true
|
||||||
|
:thickness 9
|
||||||
|
:class "ram-cached-ring")
|
||||||
|
(box :orientation "v" :valign "center" :halign "center" :space-evenly false
|
||||||
|
(label :class "ram-used-label" :text "${memory.human.used}")
|
||||||
|
(label :class "ram-total-label" :text "${memory.human.total}")))
|
||||||
|
(box :orientation "v" :space-evenly false :halign "center" :valign "center"
|
||||||
|
(overlay
|
||||||
|
(circular-progress
|
||||||
|
:width 60 :height 60
|
||||||
|
:value {100*swap.used/swap.total}
|
||||||
|
:start-at 0 :clockwise true :thickness 7
|
||||||
|
:class "swap-ring"
|
||||||
|
:tooltip "Swap\n${swap.human.used} / ${swap.human.total}")
|
||||||
|
(box :orientation "v" :valign "center" :halign "center" :space-evenly false
|
||||||
|
(label :class "ram-used-label" :text "${swap.human.used}")
|
||||||
|
(label :class "ram-total-label" :text "${swap.human.total}"))))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
(defwidget ram-sys-win []
|
; --- Battery ---
|
||||||
(box
|
|
||||||
:orientation "v"
|
(defwidget bat-sys-win []
|
||||||
:space-evenly false
|
(box :orientation "v" :space-evenly false :class "sys-section" :visible {battery.visible}
|
||||||
(box :class "sys-label" "RAM")
|
(section-header :title "Battery" :accent "bat-accent")
|
||||||
(progress
|
(overlay
|
||||||
:value {100*memory.used/memory.total}
|
(circular-progress
|
||||||
:orientation "h"
|
:width 60 :height 60
|
||||||
:flipped true
|
:value {battery.percentage}
|
||||||
:class "memory-usage"
|
:start-at 0
|
||||||
:tooltip "RAM"
|
:clockwise true
|
||||||
)
|
:thickness 6
|
||||||
|
:class "bat-ring"
|
||||||
|
:style "color: ${battery.color};"
|
||||||
|
:tooltip "Battery ${round(battery.percentage, 0)}%\n${battery.status} @ ${battery.wattage}")
|
||||||
|
(label :class "bat-ring-label"
|
||||||
|
:text "${round(battery.percentage, 0)}%"))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
; --- Root ---
|
||||||
|
|
||||||
(defwidget sys-win []
|
(defwidget sys-win []
|
||||||
(box
|
(box
|
||||||
:class "sys-win"
|
:class "sys-win"
|
||||||
:space-evenly false
|
:space-evenly false
|
||||||
:orientation "h"
|
:orientation "v"
|
||||||
(box
|
(cpu-sys-win)
|
||||||
:space-evenly false
|
(box :class "section-sep")
|
||||||
:orientation "v"
|
(gpu-sys-win)
|
||||||
(cpu-sys-win)
|
(box :class "section-sep")
|
||||||
(box :class "spacer")
|
(ram-sys-win)
|
||||||
(box :class "spacer")
|
(box :class "section-sep" :visible {battery.visible})
|
||||||
(gpu-sys-win)
|
(bat-sys-win)
|
||||||
(box :class "spacer")
|
|
||||||
(box :class "spacer")
|
|
||||||
(ram-sys-win)
|
|
||||||
)
|
|
||||||
(box
|
|
||||||
:visible {battery.visible}
|
|
||||||
:height 200
|
|
||||||
(graph
|
|
||||||
:height 200
|
|
||||||
:value {battery.percentage}
|
|
||||||
:time-range "30min"
|
|
||||||
:min "0.0"
|
|
||||||
:max "100.0"
|
|
||||||
:dynamic true
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -134,7 +216,7 @@
|
|||||||
:geometry (geometry
|
:geometry (geometry
|
||||||
:x "0%"
|
:x "0%"
|
||||||
:y "0%"
|
:y "0%"
|
||||||
:anchor "bottom right"
|
:anchor "bottom right"
|
||||||
:width "0px"
|
:width "320px"
|
||||||
:height "0px")
|
:height "0px")
|
||||||
(window (sys-win)))
|
(window (sys-win)))
|
||||||
|
|||||||
@@ -24,7 +24,6 @@
|
|||||||
|
|
||||||
|
|
||||||
startupScript = pkgs.writeShellScriptBin "hyprland-start" ''
|
startupScript = pkgs.writeShellScriptBin "hyprland-start" ''
|
||||||
ags-run &
|
|
||||||
awww-daemon &
|
awww-daemon &
|
||||||
|
|
||||||
sleep 2
|
sleep 2
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{ ... }: {
|
{ ... }: {
|
||||||
services.fwupd.enable = true;
|
services.fwupd.enable = true;
|
||||||
hardware.enableAllFirmware = true;
|
hardware.enableAllFirmware = false;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user