Update astal

Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
2025-06-30 00:39:34 +01:00
parent b8c43dc5d8
commit 68e6eddd22
15 changed files with 132 additions and 114 deletions

31
flake.lock generated
View File

@@ -5,23 +5,23 @@
"astal": [
"astal"
],
"gnim": "gnim",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1744557573,
"narHash": "sha256-XAyj0iDuI51BytJ1PwN53uLpzTDdznPDQFG4RwihlTQ=",
"lastModified": 1751192975,
"narHash": "sha256-X2WQxQZX9aktyaFQW94a4eCO0BYkLm9FZr9dyjVS7Sg=",
"owner": "aylur",
"repo": "ags",
"rev": "3ed9737bdbc8fc7a7c7ceef2165c9109f336bff6",
"rev": "74cdd7eabf0884a7d5ba0b300849891a7e89697e",
"type": "github"
},
"original": {
"owner": "aylur",
"ref": "main",
"repo": "ags",
"rev": "3ed9737bdbc8fc7a7c7ceef2165c9109f336bff6",
"type": "github"
}
},
@@ -32,18 +32,17 @@
]
},
"locked": {
"lastModified": 1749559749,
"narHash": "sha256-TM95tg1G7S6rVBBoMwurXMz8Il4xlnuZ2TI4h6lfZzg=",
"lastModified": 1751126708,
"narHash": "sha256-AodIKw7TmI7rHVcOfEsO82stupMYIMVQeLAUQfVxnkU=",
"owner": "aylur",
"repo": "astal",
"rev": "dd8a4662f2f17fb4326a7bd0fb2d054f5d477ba3",
"rev": "ac90f09385a2295da9fdc108aaba4a317aaeacc7",
"type": "github"
},
"original": {
"owner": "aylur",
"ref": "main",
"repo": "astal",
"rev": "dd8a4662f2f17fb4326a7bd0fb2d054f5d477ba3",
"type": "github"
}
},
@@ -110,6 +109,22 @@
"type": "github"
}
},
"gnim": {
"flake": false,
"locked": {
"lastModified": 1751120710,
"narHash": "sha256-sT1ILM8m1QG8CeMmqLHhW/8T/MzUq3JL9jO3V7FMa4w=",
"owner": "aylur",
"repo": "gnim",
"rev": "5d2b734be452e2819f3a7313dbb34fa43c23e5d9",
"type": "github"
},
"original": {
"owner": "aylur",
"repo": "gnim",
"type": "github"
}
},
"home-manager": {
"inputs": {
"nixpkgs": [

View File

@@ -99,9 +99,6 @@
repo = "astal";
ref = "main";
# TODO: Update
rev = "dd8a4662f2f17fb4326a7bd0fb2d054f5d477ba3";
inputs.nixpkgs.follows = "nixpkgs";
};
@@ -111,9 +108,6 @@
repo = "ags";
ref = "main";
# TODO: Update
rev = "3ed9737bdbc8fc7a7c7ceef2165c9109f336bff6";
inputs = {
nixpkgs.follows = "nixpkgs";
astal.follows = "astal";

View File

@@ -1,6 +1,6 @@
import { App } from "astal/gtk3";
import { monitorFile } from "astal/file";
import { exec } from "astal/process";
import app from "ags/gtk3/app";
import { exec } from "ags/process";
import { monitorFile } from "ags/file";
import GLib from "gi://GLib";
import Left from "./widget/Left";
import Center from "./widget/Center";
@@ -12,15 +12,15 @@ const scss = `${HOME}/.config/astal/theme.sass`;
monitorFile(scss, () => {
exec(`sassc ${scss} ${css}`);
App.apply_css(css, true);
app.apply_css(css, true);
});
exec(`sassc ${scss} ${css}`);
App.start({
app.start({
css,
main() {
App.get_monitors().map((monitor) => {
app.get_monitors().map((monitor) => {
Left(monitor);
Center(monitor);
Right(monitor);

View File

@@ -1,8 +1,8 @@
import { Gdk } from "astal/gtk3";
import { Gdk } from "ags/gtk3";
import Hyprland from "gi://AstalHyprland";
export const range = (length: number, start = 1) => {
return Array.from({ length }, (n, i) => i + start);
return Array.from({ length }, (_, i) => i + start);
};
export const getHyprlandMonitor = (gdkmonitor: Gdk.Monitor) => {

View File

@@ -1,6 +1,5 @@
{
"name": "astal-shell",
"dependencies": {
"astal": "~/.local/share/ags"
"ags": "*"
}
}

View File

@@ -1,12 +1,12 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"experimentalDecorators": true,
"strict": true,
"target": "ES2022",
"jsx": "react-jsx",
"jsxImportSource": "ags/gtk3",
"lib": ["ES2023"],
"module": "ES2022",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"jsxImportSource": "astal/gtk3"
"strict": true,
"target": "ES2020"
}
}

View File

@@ -1,17 +1,19 @@
import { App, Astal, Gtk, Gdk } from "astal/gtk3";
import { Astal, Gtk, Gdk } from "ags/gtk3";
import Date from "./components/Date";
import Hidden from "./components/Hidden";
import app from "ags/gtk3/app";
export default (monitor: Gdk.Monitor) => (
<window
className="root"
visible
class="root"
gdkmonitor={monitor}
exclusivity={Astal.Exclusivity.IGNORE}
anchor={Astal.WindowAnchor.TOP}
application={App}
application={app}
>
<Hidden>
<box className="widgets" hexpand halign={Gtk.Align.CENTER}>
<box class="widgets" hexpand halign={Gtk.Align.CENTER}>
<Date />
</box>
</Hidden>

View File

@@ -1,21 +1,22 @@
import { App, Astal, Gtk, Gdk } from "astal/gtk3";
import { Astal, Gtk, Gdk } from "ags/gtk3";
import app from "ags/gtk3/app";
import Launcher from "./components/Launcher";
import Workspace from "./components/Workspaces";
import Hidden from "./components/Hidden";
import { getHyprlandMonitor } from "../lib";
export default (monitor: Gdk.Monitor) => (
<window
className="root"
visible
class="root"
gdkmonitor={monitor}
exclusivity={Astal.Exclusivity.IGNORE}
anchor={Astal.WindowAnchor.TOP | Astal.WindowAnchor.LEFT}
application={App}
application={app}
>
<Hidden>
<box className="widgets" hexpand halign={Gtk.Align.START}>
<box class="widgets" hexpand halign={Gtk.Align.START}>
<Launcher />
<Workspace monitor={getHyprlandMonitor(monitor)!} />
<Workspace gdkmonitor={monitor} />
</box>
</Hidden>
</window>

View File

@@ -1,18 +1,21 @@
import { App, Astal, Gtk, Gdk } from "astal/gtk3";
import { Astal, Gtk } from "ags/gtk3";
import app from "ags/gtk3/app";
import Gdk from "gi://Gdk";
import Systray from "./components/Tray";
import Hidden from "./components/Hidden";
import Battery from "./components/Battery";
export default (monitor: Gdk.Monitor) => (
<window
className="root"
visible
class="root"
gdkmonitor={monitor}
exclusivity={Astal.Exclusivity.IGNORE}
anchor={Astal.WindowAnchor.TOP | Astal.WindowAnchor.RIGHT}
application={App}
application={app}
>
<Hidden>
<box className="widgets" hexpand halign={Gtk.Align.END}>
<box class="widgets" hexpand halign={Gtk.Align.END}>
<Systray />
<Battery />
</box>

View File

@@ -1,4 +1,4 @@
import { bind, Variable } from "astal";
import { createBinding, createComputed } from "ags";
import AstalBattery from "gi://AstalBattery";
const battery = AstalBattery.get_default();
@@ -9,19 +9,19 @@ const formatTime = (seconds: number) =>
: "--:--";
export default () => {
const percentage = bind(battery, "percentage").as(
const percentage = createBinding(battery, "percentage").as(
(p) => Math.round(p * 100) + "%",
);
const charging = bind(battery, "charging");
const timeToFull = bind(battery, "timeToFull");
const timeToEmpty = bind(battery, "timeToEmpty");
const charging = createBinding(battery, "charging");
const timeToFull = createBinding(battery, "timeToFull");
const timeToEmpty = createBinding(battery, "timeToEmpty");
const time = Variable.derive(
const time = createComputed(
[charging, timeToFull, timeToEmpty],
(charging, full, empty) => formatTime(charging ? full : empty),
);
const label = Variable.derive(
const label = createComputed(
[percentage, charging, time],
(percentage, charging, time) => {
const arrow = charging ? "▲" : "▼";
@@ -30,8 +30,8 @@ export default () => {
);
return (
<button className="battery">
<label className="label" label={bind(label)} />
<button class="battery">
<label class="label" label={label} />
</button>
);
};

View File

@@ -1,21 +1,16 @@
import { bind, Variable } from "astal";
import { GLib } from "astal";
import { createPoll } from "ags/time";
import GLib from "gi://GLib?version=2.0";
export default () => {
const time = Variable(
const time = createPoll(
GLib.DateTime.new_now_local().format("%H:%M - %A, %d %B %Y")!,
).poll(
1000,
() => GLib.DateTime.new_now_local().format("%H:%M - %A, %d %B %Y")!,
);
return (
<button className="date">
<label
className="label"
onDestroy={() => time.drop()}
label={bind(time)}
/>
<button class="date">
<label class="label" label={time} />
</button>
);
};

View File

@@ -1,5 +1,6 @@
import { Gtk } from "astal/gtk3";
import { Variable, bind, timeout } from "astal";
import { createState } from "ags";
import { Gtk } from "ags/gtk3";
import { timeout } from "ags/time";
export default function Hidden({
child,
@@ -12,24 +13,20 @@ export default function Hidden({
orientation?: Gtk.Orientation;
transitionType?: Gtk.RevealerTransitionType;
}) {
const show = Variable(true);
const [show, setShow] = createState(true);
const contents = child ?? children;
return (
<eventbox
clickThrough
onHover={() => show.set(true)}
onHoverLost={() => show.set(false)}
>
<eventbox onHover={() => setShow(true)} onHoverLost={() => setShow(false)}>
<box orientation={orientation}>
<revealer
setup={(self) => timeout(2000, () => (self.revealChild = false))}
revealChild={bind(show)}
onRealize={() => timeout(2000, () => setShow(false))}
revealChild={show}
transitionType={transitionType}
>
{Array.isArray(contents) ? <>{contents}</> : contents}
</revealer>
<box clickThrough className="trigger-guard" />
<box class="trigger-guard" />
</box>
</eventbox>
);

View File

@@ -1,14 +1,14 @@
import { execAsync } from "astal/process";
import { execAsync } from "ags/process";
export default () => (
<button
className="launcher"
onClickRelease={() =>
class="launcher"
onClicked={() =>
execAsync(
'rofi -modes drun -show drun -run-command \"uwsm app -- {cmd}\"',
)
}
>
<icon className="icon" icon="nix-snowflake-symbolic" />;
<icon class="icon" icon="nix-snowflake-symbolic" />;
</button>
);

View File

@@ -1,28 +1,31 @@
import { App } from "astal/gtk3";
import { bind } from "astal";
import { createBinding, For } from "ags";
import app from "ags/gtk3/app";
import Tray from "gi://AstalTray";
const tray = Tray.get_default();
const TrayButton = ({ item }: { item: Tray.TrayItem }) => (
<menubutton
className="item"
tooltipMarkup={bind(item, "tooltipMarkup")}
class="item"
tooltipMarkup={createBinding(item, "tooltipMarkup")}
usePopover={false}
menuModel={bind(item, "menuModel")}
actionGroup={bind(item, "actionGroup").as((ag) => ["dbusmenu", ag])}
menuModel={createBinding(item, "menuModel")}
>
<icon gicon={bind(item, "gicon")} />
<icon gicon={createBinding(item, "gicon")} />
</menubutton>
);
export default () => (
<box className="systray">
{bind(tray, "items").as((items) =>
items.map((item) => {
if (item.iconThemePath) App.add_icons(item.iconThemePath);
return <TrayButton item={item} />;
}),
)}
</box>
);
export default () => {
let items = createBinding(tray, "items");
return (
<box class="systray">
<For each={items}>
{(item, _) => {
if (item.iconThemePath) app.add_icons(item.iconThemePath);
return <TrayButton item={item} />;
}}
</For>
</box>
);
};

View File

@@ -1,74 +1,83 @@
import { bind, Variable } from "astal";
import Hyprland from "gi://AstalHyprland";
import { range } from "../../lib";
import { getHyprlandMonitor, range } from "../../lib";
import {
Accessor,
createBinding,
createComputed,
createState,
Setter,
} from "ags";
import { Gdk, Gtk } from "ags/gtk3";
const hyprland = Hyprland.get_default();
const BLOCK_SIZE = 10;
const Workspace = ({ id }: { id: number }) => {
let clients: Variable<string[]>;
let clients: Accessor<string[]>;
let setClients: Setter<string[]>;
try {
const workspace = hyprland.get_workspace(id);
clients = Variable(workspace.clients.map((client) => client.address));
[clients, setClients] = createState(
workspace.clients.map((client) => client.address),
);
} catch (_) {
clients = Variable([]);
[clients, setClients] = createState<string[]>([]);
}
const active = Variable.derive(
[bind(hyprland, "focusedWorkspace")],
const active = createComputed(
[createBinding(hyprland, "focusedWorkspace")],
(focused) => focused.id == id,
);
hyprland.connect("workspace-added", (_, workspace) => {
if (workspace.id != id) return;
clients.set(workspace.clients.map((client) => client.address));
setClients(workspace.clients.map((client) => client.address));
});
hyprland.connect("workspace-removed", (_, workspaceId) => {
if (workspaceId != id) return;
clients.set([]);
setClients([]);
});
hyprland.connect("client-added", (_hyprland, client) => {
if (client.workspace.id != id) return;
clients.set([...clients.get(), client.address]);
setClients([...clients.get(), client.address]);
});
// Explicit separate event handling instead of Variable.derive(workspaces, clients)
// because client-moved events appear to be broken if done that way.
hyprland.connect("client-moved", (_hyprland, client, workspace) => {
if (workspace.id == id) {
clients.set([...clients.get(), client.address]);
setClients([...clients.get(), client.address]);
} else {
clients.set(
setClients(
clients.get().filter((oldClient) => oldClient != client.address),
);
}
});
hyprland.connect("client-removed", (_hyprland, address) => {
clients.set(clients.get().filter((oldClient) => oldClient != address));
setClients(clients.get().filter((oldClient) => oldClient != address));
});
const className = Variable.derive([active, clients], (active, clients) => {
const className = createComputed([active, clients], (active, clients) => {
if (active) return "button active";
if (clients.length > 0) return "button occupied";
return "button";
});
return (
<box vertical>
<box orientation={Gtk.Orientation.VERTICAL}>
<box vexpand />
<eventbox onClickRelease={() => hyprland.dispatch("workspace", `${id}`)}>
<label className={className()} />
<label class={className} />
</eventbox>
<box vexpand />
</box>
);
};
export default ({ monitor }: { monitor: Hyprland.Monitor }) => {
export default ({ gdkmonitor }: { gdkmonitor: Gdk.Monitor }) => {
const monitor = getHyprlandMonitor(gdkmonitor)!;
const workspaces = hyprland.get_workspaces();
const displayWorkspaces = workspaces.filter(
(w) => w.monitor.id === monitor.id,
@@ -78,7 +87,7 @@ export default ({ monitor }: { monitor: Hyprland.Monitor }) => {
return (
<eventbox
className="workspaces"
class="workspaces"
onScroll={(_, e) => {
hyprland.dispatch("workspace", e.delta_y > 0 ? "m+1" : "m-1");
}}