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": [
"astal" "astal"
], ],
"gnim": "gnim",
"nixpkgs": [ "nixpkgs": [
"nixpkgs" "nixpkgs"
] ]
}, },
"locked": { "locked": {
"lastModified": 1744557573, "lastModified": 1751192975,
"narHash": "sha256-XAyj0iDuI51BytJ1PwN53uLpzTDdznPDQFG4RwihlTQ=", "narHash": "sha256-X2WQxQZX9aktyaFQW94a4eCO0BYkLm9FZr9dyjVS7Sg=",
"owner": "aylur", "owner": "aylur",
"repo": "ags", "repo": "ags",
"rev": "3ed9737bdbc8fc7a7c7ceef2165c9109f336bff6", "rev": "74cdd7eabf0884a7d5ba0b300849891a7e89697e",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "aylur", "owner": "aylur",
"ref": "main", "ref": "main",
"repo": "ags", "repo": "ags",
"rev": "3ed9737bdbc8fc7a7c7ceef2165c9109f336bff6",
"type": "github" "type": "github"
} }
}, },
@@ -32,18 +32,17 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1749559749, "lastModified": 1751126708,
"narHash": "sha256-TM95tg1G7S6rVBBoMwurXMz8Il4xlnuZ2TI4h6lfZzg=", "narHash": "sha256-AodIKw7TmI7rHVcOfEsO82stupMYIMVQeLAUQfVxnkU=",
"owner": "aylur", "owner": "aylur",
"repo": "astal", "repo": "astal",
"rev": "dd8a4662f2f17fb4326a7bd0fb2d054f5d477ba3", "rev": "ac90f09385a2295da9fdc108aaba4a317aaeacc7",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "aylur", "owner": "aylur",
"ref": "main", "ref": "main",
"repo": "astal", "repo": "astal",
"rev": "dd8a4662f2f17fb4326a7bd0fb2d054f5d477ba3",
"type": "github" "type": "github"
} }
}, },
@@ -110,6 +109,22 @@
"type": "github" "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": { "home-manager": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
import { Gdk } from "astal/gtk3"; import { Gdk } from "ags/gtk3";
import Hyprland from "gi://AstalHyprland"; import Hyprland from "gi://AstalHyprland";
export const range = (length: number, start = 1) => { 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) => { export const getHyprlandMonitor = (gdkmonitor: Gdk.Monitor) => {

View File

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

View File

@@ -1,12 +1,12 @@
{ {
"$schema": "https://json.schemastore.org/tsconfig", "$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": { "compilerOptions": {
"experimentalDecorators": true, "jsx": "react-jsx",
"strict": true, "jsxImportSource": "ags/gtk3",
"target": "ES2022", "lib": ["ES2023"],
"module": "ES2022", "module": "ES2022",
"moduleResolution": "Bundler", "moduleResolution": "Bundler",
"jsx": "react-jsx", "strict": true,
"jsxImportSource": "astal/gtk3" "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 Date from "./components/Date";
import Hidden from "./components/Hidden"; import Hidden from "./components/Hidden";
import app from "ags/gtk3/app";
export default (monitor: Gdk.Monitor) => ( export default (monitor: Gdk.Monitor) => (
<window <window
className="root" visible
class="root"
gdkmonitor={monitor} gdkmonitor={monitor}
exclusivity={Astal.Exclusivity.IGNORE} exclusivity={Astal.Exclusivity.IGNORE}
anchor={Astal.WindowAnchor.TOP} anchor={Astal.WindowAnchor.TOP}
application={App} application={app}
> >
<Hidden> <Hidden>
<box className="widgets" hexpand halign={Gtk.Align.CENTER}> <box class="widgets" hexpand halign={Gtk.Align.CENTER}>
<Date /> <Date />
</box> </box>
</Hidden> </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 Launcher from "./components/Launcher";
import Workspace from "./components/Workspaces"; import Workspace from "./components/Workspaces";
import Hidden from "./components/Hidden"; import Hidden from "./components/Hidden";
import { getHyprlandMonitor } from "../lib";
export default (monitor: Gdk.Monitor) => ( export default (monitor: Gdk.Monitor) => (
<window <window
className="root" visible
class="root"
gdkmonitor={monitor} gdkmonitor={monitor}
exclusivity={Astal.Exclusivity.IGNORE} exclusivity={Astal.Exclusivity.IGNORE}
anchor={Astal.WindowAnchor.TOP | Astal.WindowAnchor.LEFT} anchor={Astal.WindowAnchor.TOP | Astal.WindowAnchor.LEFT}
application={App} application={app}
> >
<Hidden> <Hidden>
<box className="widgets" hexpand halign={Gtk.Align.START}> <box class="widgets" hexpand halign={Gtk.Align.START}>
<Launcher /> <Launcher />
<Workspace monitor={getHyprlandMonitor(monitor)!} /> <Workspace gdkmonitor={monitor} />
</box> </box>
</Hidden> </Hidden>
</window> </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 Systray from "./components/Tray";
import Hidden from "./components/Hidden"; import Hidden from "./components/Hidden";
import Battery from "./components/Battery"; import Battery from "./components/Battery";
export default (monitor: Gdk.Monitor) => ( export default (monitor: Gdk.Monitor) => (
<window <window
className="root" visible
class="root"
gdkmonitor={monitor} gdkmonitor={monitor}
exclusivity={Astal.Exclusivity.IGNORE} exclusivity={Astal.Exclusivity.IGNORE}
anchor={Astal.WindowAnchor.TOP | Astal.WindowAnchor.RIGHT} anchor={Astal.WindowAnchor.TOP | Astal.WindowAnchor.RIGHT}
application={App} application={app}
> >
<Hidden> <Hidden>
<box className="widgets" hexpand halign={Gtk.Align.END}> <box class="widgets" hexpand halign={Gtk.Align.END}>
<Systray /> <Systray />
<Battery /> <Battery />
</box> </box>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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