ags added

This commit is contained in:
soraefir
2026-06-12 01:18:56 +02:00
parent 526a36b6e6
commit 582b96779e
12 changed files with 1075 additions and 30 deletions

45
flake.lock generated
View File

@@ -1,5 +1,48 @@
{
"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": {
"flake": false,
"locked": {
@@ -254,6 +297,8 @@
},
"root": {
"inputs": {
"ags": "ags",
"astal": "astal",
"darwin": "darwin",
"hardware": "hardware",
"home-manager": "home-manager",

View File

@@ -35,6 +35,16 @@
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:

View File

@@ -0,0 +1,57 @@
{ 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
];
};
};
}

View File

@@ -0,0 +1,768 @@
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} />
</>
)
},
})

View File

@@ -0,0 +1,176 @@
@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;
}

View File

@@ -1 +1 @@
{ ... }: { imports = [ ./dunst ./eww ./kanshi ./waylock ./wofi ]; }
{ ... }: { imports = [ ./dunst ./eww ./kanshi ./waylock ./wofi ./ags ]; }

View File

@@ -1,12 +1,4 @@
{ config, lib, pkgs, ... }:
let
restartEwwBar = monitor: pkgs.writeShellScript "restart-eww-bar-after-kanshi-${toString monitor}" ''
sleep 1
${lib.getExe pkgs.eww} close bar || true
${lib.getExe pkgs.eww} open bar --screen ${toString monitor}
'';
in {
{ config, lib, ... }: {
config = lib.mkIf (config.usercfg.wm == "Wayland") {
services.kanshi = {
@@ -15,7 +7,6 @@ in {
settings = [
{
profile.name = "tower_0";
profile.exec = [ "${restartEwwBar 1}" ];
profile.outputs = [
{
criteria = "AOC 24E1W1 GNSKCHA086899";
@@ -37,7 +28,6 @@ in {
}
{
profile.name = "tower_1";
profile.exec = [ "${restartEwwBar 1}" ];
profile.outputs = [
{
criteria = "AOC 24E1W1 GNSKCHA086899";
@@ -67,7 +57,6 @@ in {
}
{
profile.name = "laptop_0";
profile.exec = [ "${restartEwwBar 0}" ];
profile.outputs = [{
criteria = "LG Display 0x060A Unknown";
mode = "1920x1080@60.020";
@@ -78,7 +67,6 @@ in {
}
{
profile.name = "laptop_1";
profile.exec = [ "${restartEwwBar 1}" ];
profile.outputs = [
{
criteria = "CEX CX133 0x00000001";
@@ -98,7 +86,6 @@ in {
}
{
profile.name = "laptop_2";
profile.exec = [ "${restartEwwBar 1}" ];
profile.outputs = [
{
criteria = "AOC 16G3 1DDP7HA000348";

View File

@@ -24,7 +24,7 @@
startupScript = pkgs.writeShellScriptBin "hyprland-start" ''
eww-open-on-current-screen bar &
ags-run &
awww-daemon &
sleep 2

12
overlays/ags/default.nix Normal file
View File

@@ -0,0 +1,12 @@
{ final, prev, ... }:
prev.ags.overrideAttrs (old: rec {
version = "3.1.2";
src = prev.fetchFromGitHub {
owner = "Aylur";
repo = "ags";
rev = "v${version}";
hash = "sha256-tM3s7CX+tgxlYW0Sk3nzVThg2MHn08foIuMxABupxIs=";
};
modRoot = "cli";
vendorHash = "sha256-UHMHbUGqJeUTw0AHHyTdQ8ed5z+SFyPcdXs4shC+hoI=";
})

View File

@@ -1,10 +0,0 @@
{ final, prev, ... }:
prev.bambu-studio.overrideAttrs (oldAttrs: rec{
version = "02.00.01.50";
src = prev.fetchFromGitHub {
owner = "bambulab";
repo = "BambuStudio";
rev = "v${version}";
hash = "sha256-7mkrPl2CQSfc1lRjl1ilwxdYcK5iRU//QGKmdCicK30=";
};
})

View File

@@ -4,7 +4,7 @@
#openttd-jgrpp = import ./openttd-jgrpp { inherit final prev; };
#yarn-berry = import ./yarn-berry { inherit final prev; };
#eww = import ./eww { inherit final prev; };
#bambu-studio = import ./bambu-studio { inherit final prev; };
# ags = import ./ags { inherit final prev; };
wine = final.unstable.wineWow64Packages.unstableFull;
unstable = import inputs.nixUnstable {

View File

@@ -2,16 +2,16 @@
let old = prev.eww;
in final.rustPlatform.buildRustPackage rec {
pname = "eww";
version = "98c220126d912b935987766f56650b55f3e226eb";
version = "865cf631d5bbb5f9fccc99b3f4cc80b9eeada18c";
src = prev.fetchFromGitHub {
owner = "elkowar";
repo = "eww";
rev = "${version}";
hash = "sha256-zi+5G05aakh8GBdfHL1qcNo/15VEm5mXtHGgKMAyp1U=";
hash = "sha256-zi+5G05aakh8GBdfHL1qcNo/15aEm5mXtHGgKMAyp1U=";
};
cargoHash = "sha256-SEdr9nW5nBm1g6fjC5fZhqPbHQ7H6Kk0RL1V6OEQRdA=";
cargoHash = "sha256-SEdr9nW5nBm1gafjC5fZhqPbHQ7H6Kk0RL1V6OEQRdA=";
nativeBuildInputs = old.nativeBuildInputs;
buildInputs = old.buildInputs ++ [ final.libdbusmenu-gtk3 ];