summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/modules/bar.tsx152
-rw-r--r--src/modules/launcher.tsx23
-rw-r--r--src/modules/notifpopups.tsx2
-rw-r--r--src/modules/osds.tsx18
-rw-r--r--src/utils/system.ts16
-rw-r--r--src/widgets/notification.tsx5
-rw-r--r--src/widgets/popupwindow.ts2
7 files changed, 133 insertions, 85 deletions
diff --git a/src/modules/bar.tsx b/src/modules/bar.tsx
index cf3ce5e..c1c3dfc 100644
--- a/src/modules/bar.tsx
+++ b/src/modules/bar.tsx
@@ -8,7 +8,7 @@ import type { AstalWidget } from "@/utils/types";
import { setupCustomTooltip } from "@/utils/widgets";
import type PopupWindow from "@/widgets/popupwindow";
import { execAsync, Variable } from "astal";
-import { bind, kebabify } from "astal/binding";
+import Binding, { bind, kebabify } from "astal/binding";
import { App, Astal, Gtk } from "astal/gtk3";
import { bar as config } from "config";
import AstalBattery from "gi://AstalBattery";
@@ -17,7 +17,7 @@ import AstalHyprland from "gi://AstalHyprland";
import AstalNetwork from "gi://AstalNetwork";
import AstalNotifd from "gi://AstalNotifd";
import AstalTray from "gi://AstalTray";
-import AstalWp01 from "gi://AstalWp";
+import AstalWp from "gi://AstalWp";
const hyprland = AstalHyprland.get_default();
@@ -83,7 +83,7 @@ const OSIcon = () => (
const ActiveWindow = () => (
<box
- vertical={config.vertical}
+ vertical={bind(config.vertical)}
className="module active-window"
setup={self => {
const title = Variable("");
@@ -109,14 +109,15 @@ const ActiveWindow = () => (
}
/>
<label
- angle={config.vertical ? 270 : 0}
- setup={self =>
- hookFocusedClientProp(
- self,
- "title",
- c => (self.label = c?.title ? ellipsize(c.title, config.vertical ? 25 : 40) : "Desktop")
- )
- }
+ angle={bind(config.vertical).as(v => (v ? 270 : 0))}
+ setup={self => {
+ const update = () =>
+ (self.label = hyprland.focusedClient?.title
+ ? ellipsize(hyprland.focusedClient.title, config.vertical.get() ? 25 : 40)
+ : "Desktop");
+ hookFocusedClientProp(self, "title", update);
+ self.hook(config.vertical, update);
+ }}
/>
</box>
);
@@ -139,7 +140,7 @@ const MediaPlaying = () => {
setupCustomTooltip(self, bind(label));
}}
>
- <box vertical={config.vertical} className="module media-playing">
+ <box vertical={bind(config.vertical)} className="module media-playing">
<icon
setup={self =>
players.hookLastPlayer(self, "notify::identity", () => {
@@ -155,12 +156,14 @@ const MediaPlaying = () => {
}
/>
<label
- angle={config.vertical ? 270 : 0}
- setup={self =>
- players.hookLastPlayer(self, ["notify::title", "notify::artist"], () => {
- self.label = ellipsize(getLabel("No media"), config.vertical ? 25 : 40); // TODO: scroll text when playing or hover
- })
- }
+ angle={bind(config.vertical).as(v => (v ? 270 : 0))}
+ setup={self => {
+ // TODO: scroll text when playing or hover
+ const update = () =>
+ (self.label = ellipsize(getLabel("No media"), config.vertical.get() ? 25 : 40));
+ players.hookLastPlayer(self, ["notify::title", "notify::artist"], update);
+ self.hook(config.vertical, update);
+ }}
/>
</box>
</button>
@@ -169,8 +172,8 @@ const MediaPlaying = () => {
const Workspace = ({ idx }: { idx: number }) => {
let wsId = hyprland.focusedWorkspace
- ? Math.floor((hyprland.focusedWorkspace.id - 1) / config.modules.workspaces.shown) *
- config.modules.workspaces.shown +
+ ? Math.floor((hyprland.focusedWorkspace.id - 1) / config.modules.workspaces.shown.get()) *
+ config.modules.workspaces.shown.get() +
idx
: idx;
return (
@@ -184,16 +187,18 @@ const Workspace = ({ idx }: { idx: number }) => {
"occupied",
hyprland.clients.some(c => c.workspace?.id === wsId)
);
-
- self.hook(hyprland, "notify::focused-workspace", () => {
+ const updateWs = () => {
if (!hyprland.focusedWorkspace) return;
wsId =
- Math.floor((hyprland.focusedWorkspace.id - 1) / config.modules.workspaces.shown) *
- config.modules.workspaces.shown +
+ Math.floor((hyprland.focusedWorkspace.id - 1) / config.modules.workspaces.shown.get()) *
+ config.modules.workspaces.shown.get() +
idx;
self.toggleClassName("focused", hyprland.focusedWorkspace.id === wsId);
update();
- });
+ };
+
+ self.hook(config.modules.workspaces.shown, updateWs);
+ self.hook(hyprland, "notify::focused-workspace", () => updateWs);
self.hook(hyprland, "client-added", update);
self.hook(hyprland, "client-moved", update);
self.hook(hyprland, "client-removed", update);
@@ -214,10 +219,10 @@ const Workspaces = () => (
hyprland.dispatch("workspace", (event.delta_y < 0 ? "-" : "+") + 1);
}}
>
- <box vertical={config.vertical} className="module workspaces">
- {Array.from({ length: config.modules.workspaces.shown }).map((_, idx) => (
- <Workspace idx={idx + 1} /> // Start from 1
- ))}
+ <box vertical={bind(config.vertical)} className="module workspaces">
+ {bind(config.modules.workspaces.shown).as(
+ n => Array.from({ length: n }).map((_, idx) => <Workspace idx={idx + 1} />) // Start from 1
+ )}
</box>
</eventbox>
);
@@ -226,7 +231,7 @@ const TrayItem = (item: AstalTray.TrayItem) => (
<menubutton
onButtonPressEvent={(_, event) => event.get_button()[1] === Astal.MouseButton.SECONDARY && item.activate(0, 0)}
usePopover={false}
- direction={config.vertical ? Gtk.ArrowType.RIGHT : Gtk.ArrowType.DOWN}
+ direction={bind(config.vertical).as(v => (v ? Gtk.ArrowType.RIGHT : Gtk.ArrowType.DOWN))}
menuModel={bind(item, "menuModel")}
actionGroup={bind(item, "actionGroup").as(a => ["dbusmenu", a])}
setup={self => setupCustomTooltip(self, bind(item, "tooltipMarkup"))}
@@ -237,7 +242,7 @@ const TrayItem = (item: AstalTray.TrayItem) => (
const Tray = () => (
<box
- vertical={config.vertical}
+ vertical={bind(config.vertical)}
className="module tray"
visible={bind(AstalTray.get_default(), "items").as(i => i.length > 0)}
>
@@ -370,7 +375,7 @@ const BluetoothDevice = (device: AstalBluetooth.Device) => (
);
const Bluetooth = () => (
- <box vertical={config.vertical} className="bluetooth">
+ <box vertical={bind(config.vertical)} className="bluetooth">
<button
onClick={(self, event) => {
if (event.button === Astal.MouseButton.PRIMARY) togglePopup(self, event, "bluetooth-devices");
@@ -416,7 +421,7 @@ const Bluetooth = () => (
);
const StatusIcons = () => (
- <box vertical={config.vertical} className="module status-icons">
+ <box vertical={bind(config.vertical)} className="module status-icons">
<Network />
<Bluetooth />
</box>
@@ -432,7 +437,7 @@ const PkgUpdates = () => (
)
}
>
- <box vertical={config.vertical} className="module pkg-updates">
+ <box vertical={bind(config.vertical)} className="module pkg-updates">
<label className="icon" label="download" />
<label label={bind(Updates.get_default(), "numUpdates").as(String)} />
</box>
@@ -453,7 +458,7 @@ const NotifCount = () => (
)
}
>
- <box vertical={config.vertical} className="module notif-count">
+ <box vertical={bind(config.vertical)} className="module notif-count">
<label className="icon" label="info" />
<label label={bind(AstalNotifd.get_default(), "notifications").as(n => String(n.length))} />
</box>
@@ -472,7 +477,7 @@ const Battery = () => {
return (
<box
- vertical={config.vertical}
+ vertical={bind(config.vertical)}
className={bind(className)}
setup={self => setupCustomTooltip(self, bind(tooltip))}
onDestroy={() => {
@@ -489,11 +494,20 @@ const Battery = () => {
const DateTime = () => (
<button
onClick={(self, event) => event.button === Astal.MouseButton.PRIMARY && togglePopup(self, event, "sideright")}
- setup={self => setupCustomTooltip(self, bindCurrentTime(config.modules.dateTime.detailedFormat))}
+ setup={self =>
+ setupCustomTooltip(self, bindCurrentTime(bind(config.modules.dateTime.detailedFormat), undefined, self))
+ }
>
<box className="module date-time">
<label className="icon" label="calendar_month" />
- <label label={bindCurrentTime(config.modules.dateTime.format)} />
+ <label
+ setup={self =>
+ self.hook(
+ bindCurrentTime(bind(config.modules.dateTime.format), undefined, self),
+ (_, t) => (self.label = t)
+ )
+ }
+ />
</box>
</button>
);
@@ -501,7 +515,9 @@ const DateTime = () => (
const DateTimeVertical = () => (
<button
onClick={(self, event) => event.button === Astal.MouseButton.PRIMARY && togglePopup(self, event, "sideright")}
- setup={self => setupCustomTooltip(self, bindCurrentTime(config.modules.dateTime.detailedFormat))}
+ setup={self =>
+ setupCustomTooltip(self, bindCurrentTime(bind(config.modules.dateTime.detailedFormat), undefined, self))
+ }
>
<box vertical className="module date-time">
<label className="icon" label="calendar_month" />
@@ -519,22 +535,34 @@ const Power = () => (
/>
);
+const Dummy = () => <box visible={false} />; // Invisible box cause otherwise shows as text
+
+const bindWidget = (module: keyof typeof config.modules, Widget: () => JSX.Element) =>
+ bind(config.modules[module].enabled).as(e => (e ? <Widget /> : <Dummy />));
+
+const bindCompositeWidget = (module: keyof typeof config.modules, binding: Binding<JSX.Element>) =>
+ bind(Variable.derive([config.modules[module].enabled, binding], (e, w) => (e ? w : <Dummy />)));
+
export default ({ monitor }: { monitor: Monitor }) => (
<window
namespace="caelestia-bar"
monitor={monitor.id}
- anchor={
- Astal.WindowAnchor.TOP |
- Astal.WindowAnchor.LEFT |
- (config.vertical ? Astal.WindowAnchor.BOTTOM : Astal.WindowAnchor.RIGHT)
- }
+ anchor={bind(config.vertical).as(
+ v =>
+ Astal.WindowAnchor.TOP |
+ Astal.WindowAnchor.LEFT |
+ (v ? Astal.WindowAnchor.BOTTOM : Astal.WindowAnchor.RIGHT)
+ )}
exclusivity={Astal.Exclusivity.EXCLUSIVE}
>
- <centerbox vertical={config.vertical} className={`bar ${config.vertical ? "vertical" : " horizontal"}`}>
- <box vertical={config.vertical}>
- {config.modules.osIcon.enabled && <OSIcon />}
- {config.modules.activeWindow.enabled && <ActiveWindow />}
- {config.modules.mediaPlaying.enabled && <MediaPlaying />}
+ <centerbox
+ vertical={bind(config.vertical)}
+ className={bind(config.vertical).as(v => `bar ${v ? "vertical" : " horizontal"}`)}
+ >
+ <box vertical={bind(config.vertical)}>
+ {bindWidget("osIcon", OSIcon)}
+ {bindWidget("activeWindow", ActiveWindow)}
+ {bindWidget("mediaPlaying", MediaPlaying)}
<button
expand
onScroll={(_, event) =>
@@ -542,25 +570,31 @@ export default ({ monitor }: { monitor: Monitor }) => (
}
/>
</box>
- {config.modules.workspaces.enabled && <Workspaces />}
- <box vertical={config.vertical}>
+ {bindWidget("workspaces", Workspaces)}
+ <box vertical={bind(config.vertical)}>
<button
expand
onScroll={(_, event) => {
- const speaker = AstalWp01.get_default()?.audio.defaultSpeaker;
+ const speaker = AstalWp.get_default()?.audio.defaultSpeaker;
if (!speaker) return;
speaker.mute = false;
if (event.delta_y > 0) speaker.volume -= 0.1;
else speaker.volume += 0.1;
}}
/>
- {config.modules.tray.enabled && <Tray />}
- {config.modules.statusIcons.enabled && <StatusIcons />}
- {config.modules.pkgUpdates.enabled && <PkgUpdates />}
- {config.modules.notifCount.enabled && <NotifCount />}
- {config.modules.battery.enabled && AstalBattery.get_default().isBattery && <Battery />}
- {config.modules.dateTime.enabled && (config.vertical ? <DateTimeVertical /> : <DateTime />)}
- {config.modules.power.enabled && <Power />}
+ {bindWidget("tray", Tray)}
+ {bindWidget("statusIcons", StatusIcons)}
+ {bindWidget("pkgUpdates", PkgUpdates)}
+ {bindWidget("notifCount", NotifCount)}
+ {bindCompositeWidget(
+ "battery",
+ bind(AstalBattery.get_default(), "isBattery").as(b => (b ? <Battery /> : <Dummy />))
+ )}
+ {bindCompositeWidget(
+ "dateTime",
+ bind(config.vertical).as(v => (v ? <DateTimeVertical /> : <DateTime />))
+ )}
+ {bindWidget("power", Power)}
</box>
</centerbox>
</window>
diff --git a/src/modules/launcher.tsx b/src/modules/launcher.tsx
index 5adc86e..b7d4c01 100644
--- a/src/modules/launcher.tsx
+++ b/src/modules/launcher.tsx
@@ -47,8 +47,8 @@ const getEmptyTextFromMode = (mode: Mode) => {
}
};
-const limitLength = <T,>(arr: T[], cfg: { maxResults: number }) =>
- cfg.maxResults > 0 && arr.length > cfg.maxResults ? arr.slice(0, cfg.maxResults) : arr;
+const limitLength = <T,>(arr: T[], cfg: { maxResults: Variable<number> }) =>
+ cfg.maxResults.get() > 0 && arr.length > cfg.maxResults.get() ? arr.slice(0, cfg.maxResults.get()) : arr;
const close = (self: JSX.Element) => {
const toplevel = self.get_toplevel();
@@ -117,7 +117,7 @@ const PinnedApp = (names: string[]) => {
return widget;
};
-const PinnedApps = () => <box homogeneous>{config.apps.pins.map(PinnedApp)}</box>;
+const PinnedApps = () => <box homogeneous>{bind(config.apps.pins).as(p => p.map(PinnedApp))}</box>;
const SearchEntry = ({ entry }: { entry: Widget.Entry }) => (
<stack
@@ -560,7 +560,7 @@ const Results = ({ entry, mode }: { entry: Widget.Entry; mode: Variable<Mode> })
// Create todo, notify and close
execAsync(`tod t q -c ${args.join(" ")}`).catch(console.error);
- if (config.todo.notify)
+ if (config.todo.notify.get())
notify({
summary: "Todo created",
body: `Created todo with content: ${args.join(" ")}`,
@@ -616,7 +616,7 @@ const Results = ({ entry, mode }: { entry: Widget.Entry; mode: Variable<Mode> })
};
const fileSearch = () =>
- execAsync(["fd", ...config.files.fdOpts, entry.text, HOME])
+ execAsync(["fd", ...config.files.fdOpts.get(), entry.text, HOME])
.then(out => {
const paths = out.split("\n").filter(path => path);
self.foreach(ch => ch.destroy());
@@ -638,13 +638,16 @@ const Results = ({ entry, mode }: { entry: Widget.Entry; mode: Variable<Mode> })
if (entry.text) {
const clients = fuzzysort.go(entry.text, unsortedClients, {
all: true,
- limit: config.windows.maxResults < 0 ? undefined : config.windows.maxResults,
+ limit:
+ config.windows.maxResults.get() < 0
+ ? undefined
+ : config.windows.maxResults.get(),
keys: ["title", "class", "initialTitle", "initialClass"],
scoreFn: r =>
- r[0].score * config.windows.weights.title +
- r[1].score * config.windows.weights.class +
- r[2].score * config.windows.weights.initialTitle +
- r[3].score * config.windows.weights.initialClass,
+ r[0].score * config.windows.weights.title.get() +
+ r[1].score * config.windows.weights.class.get() +
+ r[2].score * config.windows.weights.initialTitle.get() +
+ r[3].score * config.windows.weights.initialClass.get(),
});
self.foreach(ch => ch.destroy());
for (const { obj } of clients)
diff --git a/src/modules/notifpopups.tsx b/src/modules/notifpopups.tsx
index 9e34549..f1ae9b6 100644
--- a/src/modules/notifpopups.tsx
+++ b/src/modules/notifpopups.tsx
@@ -40,7 +40,7 @@ export default () => (
);
// Limit number of popups
- if (config.maxPopups > 0 && self.children.length > config.maxPopups)
+ if (config.maxPopups.get() > 0 && self.children.length > config.maxPopups.get())
map.values().next().value?.destroyWithAnims();
});
self.hook(notifd, "resolved", (_, id) => map.get(id)?.destroyWithAnims());
diff --git a/src/modules/osds.tsx b/src/modules/osds.tsx
index f95053e..4bbc87b 100644
--- a/src/modules/osds.tsx
+++ b/src/modules/osds.tsx
@@ -1,6 +1,6 @@
import Monitors, { type Monitor } from "@/services/monitors";
import PopupWindow from "@/widgets/popupwindow";
-import { execAsync, register, timeout, Variable, type Time } from "astal";
+import { bind, execAsync, register, timeout, Variable, type Time } from "astal";
import { App, Astal, Gtk, Widget } from "astal/gtk3";
import cairo from "cairo";
import { osds as config } from "config";
@@ -52,13 +52,13 @@ const SliderOsd = ({
name={type}
monitor={monitor?.id}
keymode={Astal.Keymode.NONE}
- anchor={config[type].position}
- margin={config[type].margin}
+ anchor={bind(config[type].position)}
+ margin={bind(config[type].margin)}
setup={self => {
let time: Time | null = null;
const hideAfterTimeout = () => {
time?.cancel();
- time = timeout(config[type].hideDelay, () => self.hide());
+ time = timeout(config[type].hideDelay.get(), () => self.hide());
};
self.connect("show", hideAfterTimeout);
windowSetup(self, () => {
@@ -74,8 +74,8 @@ const SliderOsd = ({
setup={self => {
const halfPi = Math.PI / 2;
const vertical =
- config[type].position === Astal.WindowAnchor.LEFT ||
- config[type].position === Astal.WindowAnchor.RIGHT;
+ config[type].position.get() === Astal.WindowAnchor.LEFT ||
+ config[type].position.get() === Astal.WindowAnchor.RIGHT;
const icon = Variable("");
drawAreaSetup(self, icon);
@@ -134,7 +134,7 @@ const SliderOsd = ({
// Progress number, at top/right
let nw = 0;
let nh = 0;
- if (config[type].showValue) {
+ if (config[type].showValue.get()) {
const numLayout = parent.create_pango_layout(String(Math.round(progressValue * 100)));
[nw, nh] = numLayout.get_pixel_size();
let diff;
@@ -295,7 +295,7 @@ class LockOsd extends Widget.Window {
const child = this.get_child();
if (!child) return;
this[right ? "marginLeft" : "marginRight"] = window.visible
- ? child.get_preferred_width()[1] + config.lock.spacing
+ ? child.get_preferred_width()[1] + config.lock.spacing.get()
: 0;
}
});
@@ -311,7 +311,7 @@ class LockOsd extends Widget.Window {
super.show();
this.#update();
this.#timeout?.cancel();
- this.#timeout = timeout(config.lock[this.lockType].hideDelay, () => this.hide());
+ this.#timeout = timeout(config.lock[this.lockType].hideDelay.get(), () => this.hide());
}
}
diff --git a/src/utils/system.ts b/src/utils/system.ts
index 8180e48..7ae23dd 100644
--- a/src/utils/system.ts
+++ b/src/utils/system.ts
@@ -1,4 +1,4 @@
-import { bind, execAsync, GLib, Variable, type Gio } from "astal";
+import { bind, execAsync, GLib, Variable, type Binding, type Gio } from "astal";
import type AstalApps from "gi://AstalApps";
import { osIcons } from "./icons";
@@ -63,5 +63,15 @@ export const osIcon = (() => {
})();
export const currentTime = Variable(GLib.DateTime.new_now_local()).poll(1000, () => GLib.DateTime.new_now_local());
-export const bindCurrentTime = (format: string, fallback?: (time: GLib.DateTime) => string) =>
- bind(currentTime).as(c => c.format(format) ?? fallback?.(c) ?? new Date().toLocaleString());
+export const bindCurrentTime = (
+ format: Binding<string> | string,
+ fallback?: (time: GLib.DateTime) => string,
+ self?: JSX.Element
+) => {
+ const fmt = (c: GLib.DateTime, format: string) => c.format(format) ?? fallback?.(c) ?? new Date().toLocaleString();
+ if (typeof format === "string") return bind(currentTime).as(c => fmt(c, format));
+ if (!self) throw new Error("bindCurrentTime: self is required when format is a Binding");
+ const time = Variable.derive([currentTime, format], (c, f) => fmt(c, f));
+ self?.connect("destroy", () => time.drop());
+ return bind(time);
+};
diff --git a/src/widgets/notification.tsx b/src/widgets/notification.tsx
index 6f67b78..10f2076 100644
--- a/src/widgets/notification.tsx
+++ b/src/widgets/notification.tsx
@@ -20,7 +20,7 @@ const getTime = (time: number) => {
const now = GLib.DateTime.new_now_local();
const todayDay = now.get_day_of_year();
- if (config.agoTime) {
+ if (config.agoTime.get()) {
const diff = now.difference(messageTime) / 1e6;
if (diff < 60) return "Now";
if (diff < 3600) {
@@ -75,6 +75,7 @@ export default class Notification extends Widget.Box {
super({ className: "notification" });
const time = Variable(getTime(notification.time)).poll(60000, () => getTime(notification.time));
+ this.hook(config.agoTime, () => time.set(getTime(notification.time)));
this.#revealer = (
<revealer
@@ -138,7 +139,7 @@ export default class Notification extends Widget.Box {
});
// Close popup after timeout if transient or expire enabled in config
- if (popup && (config.expire || notification.transient))
+ if (popup && (config.expire.get() || notification.transient))
timeout(
notification.expireTimeout > 0
? notification.expireTimeout
diff --git a/src/widgets/popupwindow.ts b/src/widgets/popupwindow.ts
index 2ba49fc..624e9a5 100644
--- a/src/widgets/popupwindow.ts
+++ b/src/widgets/popupwindow.ts
@@ -38,7 +38,7 @@ export default class PopupWindow extends Widget.Window {
let marginLeft = 0;
let marginTop = 0;
- if (bar.vertical) {
+ if (bar.vertical.get()) {
marginLeft = cx + (width - x);
marginTop = cy + ((height - pHeight) / 2 - y);
if (marginTop < 0) marginTop = 0;