sideleft: create popdown window
This commit is contained in:
parent
31bc49a97a
commit
ad22dbdfeb
12 changed files with 598 additions and 21 deletions
16
config.ts
16
config.ts
|
|
@ -84,3 +84,19 @@ export const weather = {
|
||||||
location: "", // Location as a string or empty to autodetect
|
location: "", // Location as a string or empty to autodetect
|
||||||
imperial: false,
|
imperial: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const cpu = {
|
||||||
|
interval: 2000,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const gpu = {
|
||||||
|
interval: 2000,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const memory = {
|
||||||
|
interval: 5000,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const storage = {
|
||||||
|
interval: 5000,
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -114,14 +114,5 @@
|
||||||
color: scheme.$red;
|
color: scheme.$red;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: lib.s(16);
|
font-size: lib.s(16);
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:focus {
|
|
||||||
background-color: scheme.$surface1;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background-color: scheme.$surface2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,3 +4,4 @@
|
||||||
@forward "networks";
|
@forward "networks";
|
||||||
@forward "media";
|
@forward "media";
|
||||||
@forward "sideright";
|
@forward "sideright";
|
||||||
|
@forward "sideleft";
|
||||||
|
|
|
||||||
131
scss/popdowns/sideleft.scss
Normal file
131
scss/popdowns/sideleft.scss
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
@use "sass:color";
|
||||||
|
@use "../scheme";
|
||||||
|
@use "../lib";
|
||||||
|
@use "../font";
|
||||||
|
|
||||||
|
$-accent: scheme.$yellow;
|
||||||
|
|
||||||
|
.sideleft {
|
||||||
|
@include lib.rounded(8);
|
||||||
|
@include lib.border($-accent, 0.4, 2);
|
||||||
|
@include lib.shadow;
|
||||||
|
@include font.mono;
|
||||||
|
|
||||||
|
background-color: scheme.$crust;
|
||||||
|
color: $-accent;
|
||||||
|
padding: lib.s(12);
|
||||||
|
font-size: lib.s(14);
|
||||||
|
|
||||||
|
@include lib.spacing(10, true);
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
@include lib.rounded(5);
|
||||||
|
|
||||||
|
background-color: scheme.$base;
|
||||||
|
padding: lib.s(16) lib.s(20);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user {
|
||||||
|
@include lib.spacing(15);
|
||||||
|
|
||||||
|
.face {
|
||||||
|
@include lib.rounded(1000);
|
||||||
|
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
min-width: lib.s(48);
|
||||||
|
min-height: lib.s(48);
|
||||||
|
font-size: lib.s(24);
|
||||||
|
}
|
||||||
|
|
||||||
|
.details {
|
||||||
|
font-size: lib.s(14);
|
||||||
|
|
||||||
|
@include lib.spacing(3, true);
|
||||||
|
|
||||||
|
.name {
|
||||||
|
font-size: lib.s(18);
|
||||||
|
color: scheme.$text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.power {
|
||||||
|
@include lib.element-decel;
|
||||||
|
@include font.icon;
|
||||||
|
|
||||||
|
font-size: lib.s(24);
|
||||||
|
color: scheme.$red;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
color: color.mix(scheme.$red, scheme.$base, 80%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
color: color.mix(scheme.$red, scheme.$base, 60%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.locations {
|
||||||
|
@include lib.spacing(30);
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
@include lib.spacing(8, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$-colours: scheme.$pink, scheme.$yellow, scheme.$maroon, scheme.$green, scheme.$mauve, scheme.$lavender;
|
||||||
|
@for $i from 1 through 6 {
|
||||||
|
.loc#{$i} {
|
||||||
|
@include lib.element-decel;
|
||||||
|
|
||||||
|
color: nth($-colours, $i);
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
color: color.mix(nth($-colours, $i), scheme.$base, 80%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
color: color.mix(nth($-colours, $i), scheme.$base, 60%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hw-resources {
|
||||||
|
@include lib.spacing(10, true);
|
||||||
|
|
||||||
|
.resource {
|
||||||
|
font-size: lib.s(13);
|
||||||
|
|
||||||
|
.inner {
|
||||||
|
font-size: lib.s(16);
|
||||||
|
|
||||||
|
@include lib.spacing(20);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider {
|
||||||
|
@include lib.rounded(5);
|
||||||
|
@include lib.fluent-decel(1000ms);
|
||||||
|
|
||||||
|
min-height: lib.s(10);
|
||||||
|
background-color: scheme.$surface0;
|
||||||
|
color: scheme.$red;
|
||||||
|
}
|
||||||
|
|
||||||
|
$-names: gpu, cpu, memory, storage;
|
||||||
|
$-colours: scheme.$green, scheme.$maroon, scheme.$yellow, scheme.$pink;
|
||||||
|
@for $i from 1 through length($-names) {
|
||||||
|
&.#{nth($-names, $i)} {
|
||||||
|
color: nth($-colours, $i);
|
||||||
|
|
||||||
|
.slider {
|
||||||
|
color: nth($-colours, $i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,21 +1,21 @@
|
||||||
|
import type { Monitor } from "@/services/monitors";
|
||||||
|
import Players from "@/services/players";
|
||||||
|
import Updates from "@/services/updates";
|
||||||
|
import { getAppCategoryIcon } from "@/utils/icons";
|
||||||
|
import { ellipsize } from "@/utils/strings";
|
||||||
|
import { bindCurrentTime, osIcon } from "@/utils/system";
|
||||||
|
import { setupCustomTooltip } from "@/utils/widgets";
|
||||||
|
import type PopupWindow from "@/widgets/popupwindow";
|
||||||
import { execAsync, register, Variable } from "astal";
|
import { execAsync, register, Variable } from "astal";
|
||||||
import { bind, kebabify } from "astal/binding";
|
import { bind, kebabify } from "astal/binding";
|
||||||
import { App, Astal, astalify, Gdk, Gtk, type ConstructProps } from "astal/gtk3";
|
import { App, Astal, astalify, Gdk, Gtk, type ConstructProps } from "astal/gtk3";
|
||||||
|
import { bar as config } from "config";
|
||||||
import AstalBluetooth from "gi://AstalBluetooth";
|
import AstalBluetooth from "gi://AstalBluetooth";
|
||||||
import AstalHyprland from "gi://AstalHyprland";
|
import AstalHyprland from "gi://AstalHyprland";
|
||||||
import AstalNetwork from "gi://AstalNetwork";
|
import AstalNetwork from "gi://AstalNetwork";
|
||||||
import AstalNotifd from "gi://AstalNotifd";
|
import AstalNotifd from "gi://AstalNotifd";
|
||||||
import AstalTray from "gi://AstalTray";
|
import AstalTray from "gi://AstalTray";
|
||||||
import AstalWp01 from "gi://AstalWp";
|
import AstalWp01 from "gi://AstalWp";
|
||||||
import { bar as config } from "../../config";
|
|
||||||
import type { Monitor } from "../services/monitors";
|
|
||||||
import Players from "../services/players";
|
|
||||||
import Updates from "../services/updates";
|
|
||||||
import { getAppCategoryIcon } from "../utils/icons";
|
|
||||||
import { ellipsize } from "../utils/strings";
|
|
||||||
import { bindCurrentTime, osIcon } from "../utils/system";
|
|
||||||
import { setupCustomTooltip } from "../utils/widgets";
|
|
||||||
import type PopupWindow from "../widgets/popupwindow";
|
|
||||||
|
|
||||||
const hyprland = AstalHyprland.get_default();
|
const hyprland = AstalHyprland.get_default();
|
||||||
|
|
||||||
|
|
@ -44,7 +44,13 @@ const togglePopup = (self: JSX.Element, event: Astal.ClickEvent, name: string) =
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const OSIcon = () => <label className="module os-icon" label={osIcon} />;
|
const OSIcon = () => (
|
||||||
|
<button
|
||||||
|
className="module os-icon"
|
||||||
|
label={osIcon}
|
||||||
|
onClick={(self, event) => event.button === Astal.MouseButton.PRIMARY && togglePopup(self, event, "sideleft")}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
const ActiveWindow = () => (
|
const ActiveWindow = () => (
|
||||||
<box
|
<box
|
||||||
|
|
@ -453,7 +459,11 @@ const DateTime = () => (
|
||||||
);
|
);
|
||||||
|
|
||||||
const Power = () => (
|
const Power = () => (
|
||||||
<button className="module power" label="power_settings_new" onClicked={() => App.toggle_window("session")} />
|
<button
|
||||||
|
className="module power"
|
||||||
|
label="power_settings_new"
|
||||||
|
onClick={(_, event) => event.button === Astal.MouseButton.PRIMARY && App.toggle_window("session")}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default ({ monitor }: { monitor: Monitor }) => (
|
export default ({ monitor }: { monitor: Monitor }) => (
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import BluetoothDevices from "./bluetoothdevices";
|
||||||
import Media from "./media";
|
import Media from "./media";
|
||||||
import Networks from "./networks";
|
import Networks from "./networks";
|
||||||
import Notifications from "./notifications";
|
import Notifications from "./notifications";
|
||||||
|
import SideLeft from "./sideleft";
|
||||||
import SideRight from "./sideright";
|
import SideRight from "./sideright";
|
||||||
import Updates from "./updates";
|
import Updates from "./updates";
|
||||||
|
|
||||||
|
|
@ -12,6 +13,7 @@ export default () => {
|
||||||
<Networks />;
|
<Networks />;
|
||||||
<Media />;
|
<Media />;
|
||||||
<SideRight />;
|
<SideRight />;
|
||||||
|
<SideLeft />;
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
203
src/modules/popdowns/sideleft.tsx
Normal file
203
src/modules/popdowns/sideleft.tsx
Normal file
|
|
@ -0,0 +1,203 @@
|
||||||
|
import Cpu from "@/services/cpu";
|
||||||
|
import Gpu from "@/services/gpu";
|
||||||
|
import Memory from "@/services/memory";
|
||||||
|
import Storage from "@/services/storage";
|
||||||
|
import { osId } from "@/utils/system";
|
||||||
|
import PopupWindow from "@/widgets/popupwindow";
|
||||||
|
import { bind, execAsync, GLib, type Binding } from "astal";
|
||||||
|
import { App, Gtk, type Widget } from "astal/gtk3";
|
||||||
|
import type cairo from "cairo";
|
||||||
|
|
||||||
|
const fmt = (bytes: number, pow: number) => +(bytes / 1024 ** pow).toFixed(2);
|
||||||
|
const format = ({ total, used }: { total: number; used: number }) => {
|
||||||
|
if (total >= 1024 ** 4) return `${fmt(used, 4)}/${fmt(total, 4)} TiB`;
|
||||||
|
if (total >= 1024 ** 3) return `${fmt(used, 3)}/${fmt(total, 3)} GiB`;
|
||||||
|
if (total >= 1024 ** 2) return `${fmt(used, 2)}/${fmt(total, 2)} MiB`;
|
||||||
|
if (total >= 1024) return `${fmt(used, 1)}/${fmt(total, 1)} KiB`;
|
||||||
|
return `${used}/${total} B`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const User = () => (
|
||||||
|
<box className="user">
|
||||||
|
<box
|
||||||
|
valign={Gtk.Align.CENTER}
|
||||||
|
className="face"
|
||||||
|
css={`
|
||||||
|
background-image: url("${HOME}/.face");
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{!GLib.file_test(HOME + "/.face", GLib.FileTest.EXISTS) && (
|
||||||
|
<label
|
||||||
|
setup={self => {
|
||||||
|
const name = GLib.get_real_name();
|
||||||
|
if (name !== "Unknown")
|
||||||
|
self.label = name
|
||||||
|
.split(" ")
|
||||||
|
.map(s => s[0].toUpperCase())
|
||||||
|
.join("");
|
||||||
|
else self.label = "";
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</box>
|
||||||
|
<box vertical hexpand valign={Gtk.Align.CENTER} className="details">
|
||||||
|
<label xalign={0} className="name" label={GLib.get_user_name()} />
|
||||||
|
<label xalign={0} label={(GLib.getenv("XDG_CURRENT_DESKTOP") ?? osId).toUpperCase()} />
|
||||||
|
</box>
|
||||||
|
<button
|
||||||
|
valign={Gtk.Align.CENTER}
|
||||||
|
className="power"
|
||||||
|
cursor="pointer"
|
||||||
|
onClicked={() => App.toggle_window("session")}
|
||||||
|
label="power_settings_new"
|
||||||
|
/>
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
|
||||||
|
const QuickLaunch = () => (
|
||||||
|
<box className="quick-launch">
|
||||||
|
<box vertical>
|
||||||
|
<box>{/* <button */}</box> // TODO
|
||||||
|
</box>
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
|
||||||
|
const Location = ({ home, label, num }: { home?: boolean; label: string; num: number }) => (
|
||||||
|
<button
|
||||||
|
className={"loc" + num}
|
||||||
|
cursor="pointer"
|
||||||
|
onClicked={self => {
|
||||||
|
self.get_toplevel().hide();
|
||||||
|
execAsync(`xdg-open ${HOME}/${home ? "" : label.split(" ").at(-1) + "/"}`).catch(console.error);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<label xalign={0} label={label} />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
|
||||||
|
const Locations = () => (
|
||||||
|
<box className="locations">
|
||||||
|
<box vertical>
|
||||||
|
<Location label=" Downloads" num={1} />
|
||||||
|
<Location label=" Documents" num={2} />
|
||||||
|
<Location label=" Music" num={3} />
|
||||||
|
</box>
|
||||||
|
<box vertical>
|
||||||
|
<Location label=" Pictures" num={4} />
|
||||||
|
<Location label=" Videos" num={5} />
|
||||||
|
<Location label=" Home" num={6} home />
|
||||||
|
</box>
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
|
||||||
|
const Slider = ({ value }: { value: Binding<number> }) => (
|
||||||
|
<drawingarea
|
||||||
|
hexpand
|
||||||
|
valign={Gtk.Align.CENTER}
|
||||||
|
className="slider"
|
||||||
|
css={bind(value).as(v => `font-size: ${v}px;`)}
|
||||||
|
setup={self => {
|
||||||
|
const halfPi = Math.PI / 2;
|
||||||
|
|
||||||
|
const styleContext = self.get_style_context();
|
||||||
|
self.set_size_request(-1, styleContext.get_property("min-height", Gtk.StateFlags.NORMAL) as number);
|
||||||
|
|
||||||
|
self.connect("draw", (_, cr: cairo.Context) => {
|
||||||
|
const styleContext = self.get_style_context();
|
||||||
|
|
||||||
|
const width = self.get_allocated_width();
|
||||||
|
const height = styleContext.get_property("min-height", Gtk.StateFlags.NORMAL) as number;
|
||||||
|
self.set_size_request(-1, height);
|
||||||
|
|
||||||
|
const progressValue = styleContext.get_property("font-size", Gtk.StateFlags.NORMAL) as number;
|
||||||
|
let radius = styleContext.get_property("border-radius", Gtk.StateFlags.NORMAL) as number;
|
||||||
|
|
||||||
|
const bg = styleContext.get_background_color(Gtk.StateFlags.NORMAL);
|
||||||
|
cr.setSourceRGBA(bg.red, bg.green, bg.blue, bg.alpha);
|
||||||
|
|
||||||
|
// Background
|
||||||
|
cr.arc(radius, radius, radius, -Math.PI, -halfPi); // Top left
|
||||||
|
cr.arc(width - radius, radius, radius, -halfPi, 0); // Top right
|
||||||
|
cr.arc(width - radius, height - radius, radius, 0, halfPi); // Bottom right
|
||||||
|
cr.arc(radius, height - radius, radius, halfPi, Math.PI); // Bottom left
|
||||||
|
cr.fill();
|
||||||
|
|
||||||
|
// Flatten when near 0
|
||||||
|
radius = Math.min(radius, Math.min(width * progressValue, height) / 2);
|
||||||
|
|
||||||
|
const progressPosition = width * progressValue - radius;
|
||||||
|
const fg = styleContext.get_color(Gtk.StateFlags.NORMAL);
|
||||||
|
cr.setSourceRGBA(fg.red, fg.green, fg.blue, fg.alpha);
|
||||||
|
|
||||||
|
// Foreground
|
||||||
|
cr.arc(radius, radius, radius, -Math.PI, -halfPi); // Top left
|
||||||
|
cr.arc(progressPosition, radius, radius, -halfPi, 0); // Top right
|
||||||
|
cr.arc(progressPosition, height - radius, radius, 0, halfPi); // Bottom right
|
||||||
|
cr.arc(radius, height - radius, radius, halfPi, Math.PI); // Bottom left
|
||||||
|
cr.fill();
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const Resource = ({
|
||||||
|
icon,
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
labelSetup,
|
||||||
|
}: {
|
||||||
|
icon: string;
|
||||||
|
name: string;
|
||||||
|
value: Binding<number>;
|
||||||
|
labelSetup?: (self: Widget.Label) => void;
|
||||||
|
}) => (
|
||||||
|
<box vertical className={`resource ${name}`}>
|
||||||
|
<box className="inner">
|
||||||
|
<label label={icon} />
|
||||||
|
<Slider value={value.as(v => v / 100)} />
|
||||||
|
</box>
|
||||||
|
<label halign={Gtk.Align.END} label={labelSetup ? "" : value.as(v => `${+v.toFixed(2)}%`)} setup={labelSetup} />
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
|
||||||
|
const HwResources = () => (
|
||||||
|
<box vertical className="hw-resources">
|
||||||
|
{Gpu.get_default().available && <Resource icon="" name="gpu" value={bind(Gpu.get_default(), "usage")} />}
|
||||||
|
<Resource icon="" name="cpu" value={bind(Cpu.get_default(), "usage")} />
|
||||||
|
<Resource
|
||||||
|
icon=""
|
||||||
|
name="memory"
|
||||||
|
value={bind(Memory.get_default(), "usage")}
|
||||||
|
labelSetup={self => {
|
||||||
|
const mem = Memory.get_default();
|
||||||
|
const update = () => (self.label = format(mem));
|
||||||
|
self.hook(mem, "notify::used", update);
|
||||||
|
self.hook(mem, "notify::total", update);
|
||||||
|
update();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Resource
|
||||||
|
icon=""
|
||||||
|
name="storage"
|
||||||
|
value={bind(Storage.get_default(), "usage")}
|
||||||
|
labelSetup={self => {
|
||||||
|
const storage = Storage.get_default();
|
||||||
|
const update = () => (self.label = format(storage));
|
||||||
|
self.hook(storage, "notify::used", update);
|
||||||
|
self.hook(storage, "notify::total", update);
|
||||||
|
update();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default () => (
|
||||||
|
<PopupWindow name="sideleft">
|
||||||
|
<box vertical className="sideleft">
|
||||||
|
<User />
|
||||||
|
{/* <QuickLaunch /> */}
|
||||||
|
<Locations />
|
||||||
|
<HwResources />
|
||||||
|
</box>
|
||||||
|
</PopupWindow>
|
||||||
|
);
|
||||||
45
src/services/cpu.ts
Normal file
45
src/services/cpu.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { GObject, interval, property, register } from "astal";
|
||||||
|
import { cpu as config } from "config";
|
||||||
|
import GTop from "gi://GTop";
|
||||||
|
|
||||||
|
@register({ GTypeName: "Cpu" })
|
||||||
|
export default class Cpu extends GObject.Object {
|
||||||
|
static instance: Cpu;
|
||||||
|
static get_default() {
|
||||||
|
if (!this.instance) this.instance = new Cpu();
|
||||||
|
|
||||||
|
return this.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
#previous: GTop.glibtop_cpu = new GTop.glibtop_cpu();
|
||||||
|
#usage: number = 0;
|
||||||
|
|
||||||
|
@property(Number)
|
||||||
|
get usage() {
|
||||||
|
return this.#usage;
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateUsage() {
|
||||||
|
const current = new GTop.glibtop_cpu();
|
||||||
|
GTop.glibtop_get_cpu(current);
|
||||||
|
|
||||||
|
// Calculate the differences from the previous to current data
|
||||||
|
const total = current.total - this.#previous.total;
|
||||||
|
const idle = current.idle - this.#previous.idle;
|
||||||
|
|
||||||
|
this.#previous = current;
|
||||||
|
|
||||||
|
return total > 0 ? ((total - idle) / total) * 100 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
update() {
|
||||||
|
this.#usage = this.calculateUsage();
|
||||||
|
this.notify("usage");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
interval(config.interval, () => this.update());
|
||||||
|
}
|
||||||
|
}
|
||||||
55
src/services/gpu.ts
Normal file
55
src/services/gpu.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
import { execAsync, Gio, GObject, interval, property, register } from "astal";
|
||||||
|
import { gpu as config } from "config";
|
||||||
|
|
||||||
|
@register({ GTypeName: "Gpu" })
|
||||||
|
export default class Gpu extends GObject.Object {
|
||||||
|
static instance: Gpu;
|
||||||
|
static get_default() {
|
||||||
|
if (!this.instance) this.instance = new Gpu();
|
||||||
|
|
||||||
|
return this.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly available: boolean = false;
|
||||||
|
#usage: number = 0;
|
||||||
|
|
||||||
|
@property(Number)
|
||||||
|
get usage() {
|
||||||
|
return this.#usage;
|
||||||
|
}
|
||||||
|
|
||||||
|
async calculateUsage() {
|
||||||
|
const percs = (await execAsync("fish -c 'cat /sys/class/drm/card*/device/gpu_busy_percent'")).split("\n");
|
||||||
|
return percs.reduce((a, b) => a + parseFloat(b), 0) / percs.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
update() {
|
||||||
|
this.calculateUsage().then(usage => {
|
||||||
|
this.#usage = usage;
|
||||||
|
this.notify("usage");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
let enumerator = null;
|
||||||
|
try {
|
||||||
|
enumerator = Gio.File.new_for_path("/sys/class/drm").enumerate_children(
|
||||||
|
Gio.FILE_ATTRIBUTE_STANDARD_NAME,
|
||||||
|
Gio.FileQueryInfoFlags.NONE,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
let info: Gio.FileInfo | undefined | null;
|
||||||
|
while ((info = enumerator?.next_file(null))) {
|
||||||
|
if (/card[0-9]+/.test(info.get_name())) {
|
||||||
|
this.available = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.available) interval(config.interval, () => this.update());
|
||||||
|
}
|
||||||
|
}
|
||||||
60
src/services/memory.ts
Normal file
60
src/services/memory.ts
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
import { GObject, interval, property, readFileAsync, register } from "astal";
|
||||||
|
import { memory as config } from "config";
|
||||||
|
|
||||||
|
@register({ GTypeName: "Memory" })
|
||||||
|
export default class Memory extends GObject.Object {
|
||||||
|
static instance: Memory;
|
||||||
|
static get_default() {
|
||||||
|
if (!this.instance) this.instance = new Memory();
|
||||||
|
|
||||||
|
return this.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
#total: number = 0;
|
||||||
|
#free: number = 0;
|
||||||
|
#used: number = 0;
|
||||||
|
#usage: number = 0;
|
||||||
|
|
||||||
|
@property(Number)
|
||||||
|
get total() {
|
||||||
|
return this.#total;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property(Number)
|
||||||
|
get free() {
|
||||||
|
return this.#free;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property(Number)
|
||||||
|
get used() {
|
||||||
|
return this.#used;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property(Number)
|
||||||
|
get usage() {
|
||||||
|
return this.#usage;
|
||||||
|
}
|
||||||
|
|
||||||
|
async update() {
|
||||||
|
const info = await readFileAsync("/proc/meminfo");
|
||||||
|
this.#total = parseInt(info.match(/MemTotal:\s+(\d+)/)?.[1] ?? "0", 10) * 1024;
|
||||||
|
this.#free = parseInt(info.match(/MemAvailable:\s+(\d+)/)?.[1] ?? "0", 10) * 1024;
|
||||||
|
|
||||||
|
if (isNaN(this.#total)) this.#total = 0;
|
||||||
|
if (isNaN(this.#free)) this.#free = 0;
|
||||||
|
|
||||||
|
this.#used = this.#total - this.#free;
|
||||||
|
this.#usage = this.#total > 0 ? (this.#used / this.#total) * 100 : 0;
|
||||||
|
|
||||||
|
this.notify("total");
|
||||||
|
this.notify("free");
|
||||||
|
this.notify("used");
|
||||||
|
this.notify("usage");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
interval(config.interval, () => this.update().catch(console.error));
|
||||||
|
}
|
||||||
|
}
|
||||||
61
src/services/storage.ts
Normal file
61
src/services/storage.ts
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
import { GObject, interval, property, register } from "astal";
|
||||||
|
import { storage as config } from "config";
|
||||||
|
import GTop from "gi://GTop";
|
||||||
|
|
||||||
|
@register({ GTypeName: "Storage" })
|
||||||
|
export default class Storage extends GObject.Object {
|
||||||
|
static instance: Storage;
|
||||||
|
static get_default() {
|
||||||
|
if (!this.instance) this.instance = new Storage();
|
||||||
|
|
||||||
|
return this.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
#total: number = 0;
|
||||||
|
#free: number = 0;
|
||||||
|
#used: number = 0;
|
||||||
|
#usage: number = 0;
|
||||||
|
|
||||||
|
@property(Number)
|
||||||
|
get total() {
|
||||||
|
return this.#total;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property(Number)
|
||||||
|
get free() {
|
||||||
|
return this.#free;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property(Number)
|
||||||
|
get used() {
|
||||||
|
return this.#used;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property(Number)
|
||||||
|
get usage() {
|
||||||
|
return this.#usage;
|
||||||
|
}
|
||||||
|
|
||||||
|
update() {
|
||||||
|
const root = new GTop.glibtop_fsusage();
|
||||||
|
GTop.glibtop_get_fsusage(root, "/");
|
||||||
|
const home = new GTop.glibtop_fsusage();
|
||||||
|
GTop.glibtop_get_fsusage(home, "/home");
|
||||||
|
|
||||||
|
this.#total = root.blocks * root.block_size + home.blocks * home.block_size;
|
||||||
|
this.#free = root.bavail * root.block_size + home.bavail * home.block_size;
|
||||||
|
this.#used = this.#total - this.#free;
|
||||||
|
this.#usage = this.#total > 0 ? (this.#used / this.#total) * 100 : 0;
|
||||||
|
|
||||||
|
this.notify("total");
|
||||||
|
this.notify("free");
|
||||||
|
this.notify("used");
|
||||||
|
this.notify("usage");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
interval(config.interval, () => this.update());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,7 +12,9 @@
|
||||||
"jsxImportSource": "/usr/share/astal/gjs/gtk3",
|
"jsxImportSource": "/usr/share/astal/gjs/gtk3",
|
||||||
"paths": {
|
"paths": {
|
||||||
"astal": ["/usr/share/astal/gjs"],
|
"astal": ["/usr/share/astal/gjs"],
|
||||||
"astal/*": ["/usr/share/astal/gjs/*"]
|
"astal/*": ["/usr/share/astal/gjs/*"],
|
||||||
|
"config": ["./config"],
|
||||||
|
"@/*": ["./src/*"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue