From b4aca729ddae0526b66822698db7066cb09e1682 Mon Sep 17 00:00:00 2001
From: 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>
Date: Sun, 12 Jan 2025 18:00:54 +1100
Subject: bar
---
.gitignore | 1 +
app.tsx | 28 +++
assets/icons/caelestia-media-generic-symbolic.svg | 2 +
assets/icons/caelestia-media-none-symbolic.svg | 19 ++
assets/icons/caelestia-spotify-symbolic.svg | 21 ++
modules/bar.tsx | 283 ++++++++++++++++++++++
scss/_font.scss | 21 ++
scss/_lib.scss | 29 +++
scss/bar.scss | 103 ++++++++
scss/scheme/_mocha.scss | 26 ++
scss/widgets.scss | 120 +++++++++
services/apps.ts | 3 +
services/players.ts | 157 ++++++++++++
style.scss | 9 +
utils/constants.ts | 3 +
utils/icons.ts | 118 +++++++++
utils/mpris.ts | 16 ++
utils/strings.ts | 1 +
utils/system.ts | 21 ++
utils/widgets.tsx | 45 ++++
20 files changed, 1026 insertions(+)
create mode 100644 app.tsx
create mode 100644 assets/icons/caelestia-media-generic-symbolic.svg
create mode 100644 assets/icons/caelestia-media-none-symbolic.svg
create mode 100644 assets/icons/caelestia-spotify-symbolic.svg
create mode 100644 modules/bar.tsx
create mode 100644 scss/_font.scss
create mode 100644 scss/_lib.scss
create mode 100644 scss/bar.scss
create mode 100644 scss/scheme/_mocha.scss
create mode 100644 scss/widgets.scss
create mode 100644 services/apps.ts
create mode 100644 services/players.ts
create mode 100644 style.scss
create mode 100644 utils/constants.ts
create mode 100644 utils/icons.ts
create mode 100644 utils/mpris.ts
create mode 100644 utils/strings.ts
create mode 100644 utils/system.ts
create mode 100644 utils/widgets.tsx
diff --git a/.gitignore b/.gitignore
index c5d317c..89341d4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
@girs/
node_modules/
+scss/scheme/_index.scss
diff --git a/app.tsx b/app.tsx
new file mode 100644
index 0000000..4f61699
--- /dev/null
+++ b/app.tsx
@@ -0,0 +1,28 @@
+import { execAsync, GLib, writeFileAsync } from "astal";
+import { App } from "astal/gtk3";
+import AstalHyprland from "gi://AstalHyprland";
+import Bar from "./modules/bar";
+
+const loadStyleAsync = async () => {
+ if (!GLib.file_test(`${SRC}/scss/scheme/_index.scss`, GLib.FileTest.EXISTS))
+ await writeFileAsync(`${SRC}/scss/scheme/_index.scss`, '@forward "mocha";');
+ App.apply_css(await execAsync(`sass ${SRC}/style.scss`), true);
+};
+
+App.start({
+ instanceName: "caelestia",
+ icons: "assets/icons",
+ main() {
+ loadStyleAsync().catch(console.error);
+
+ AstalHyprland.get_default().monitors.forEach(m => );
+
+ console.log("Caelestia started");
+ },
+ requestHandler(request, res) {
+ if (request === "reload css") loadStyleAsync().catch(console.error);
+ else return res("Unknown command: " + request);
+ console.log(`Request handled: ${request}`);
+ res("OK");
+ },
+});
diff --git a/assets/icons/caelestia-media-generic-symbolic.svg b/assets/icons/caelestia-media-generic-symbolic.svg
new file mode 100644
index 0000000..8ff60ed
--- /dev/null
+++ b/assets/icons/caelestia-media-generic-symbolic.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/assets/icons/caelestia-media-none-symbolic.svg b/assets/icons/caelestia-media-none-symbolic.svg
new file mode 100644
index 0000000..20ea19a
--- /dev/null
+++ b/assets/icons/caelestia-media-none-symbolic.svg
@@ -0,0 +1,19 @@
+
+
+
+
\ No newline at end of file
diff --git a/assets/icons/caelestia-spotify-symbolic.svg b/assets/icons/caelestia-spotify-symbolic.svg
new file mode 100644
index 0000000..bf01823
--- /dev/null
+++ b/assets/icons/caelestia-spotify-symbolic.svg
@@ -0,0 +1,21 @@
+
+
+
\ No newline at end of file
diff --git a/modules/bar.tsx b/modules/bar.tsx
new file mode 100644
index 0000000..1db5e82
--- /dev/null
+++ b/modules/bar.tsx
@@ -0,0 +1,283 @@
+import { GLib, register, Variable } from "astal";
+import { bind, kebabify } from "astal/binding";
+import { App, Astal, astalify, Gdk, Gtk, type ConstructProps } from "astal/gtk3";
+import AstalHyprland from "gi://AstalHyprland";
+import AstalNotifd from "gi://AstalNotifd";
+import AstalTray from "gi://AstalTray";
+import Players from "../services/players";
+import { getAppCategoryIcon } from "../utils/icons";
+import { ellipsize } from "../utils/strings";
+import { osIcon } from "../utils/system";
+import { setupCustomTooltip } from "../utils/widgets";
+
+const hyprland = AstalHyprland.get_default();
+
+const wsPerGroup = 10;
+
+const hookFocusedClientProp = (
+ self: any, // Ugh why is there no base Widget type
+ prop: keyof AstalHyprland.Client,
+ callback: (c: AstalHyprland.Client | null) => void
+) => {
+ let id: number | null = null;
+ let lastClient: AstalHyprland.Client | null = null;
+ self.hook(hyprland, "notify::focused-client", () => {
+ if (id) lastClient?.disconnect(id);
+ lastClient = hyprland.focusedClient; // Can be null
+ id = lastClient?.connect(`notify::${kebabify(prop)}`, () => callback(lastClient));
+ callback(lastClient);
+ });
+ self.connect("destroy", () => id && lastClient?.disconnect(id));
+ callback(lastClient);
+};
+
+const OSIcon = () => ;
+
+const ActiveWindow = () => (
+ {
+ const title = Variable(hyprland.focusedClient?.title ?? "");
+ hookFocusedClientProp(self, "title", c => title.set(c?.title ?? ""));
+
+ const window = setupCustomTooltip(self, bind(title));
+ if (window) {
+ self.hook(title, (_, v) => !v && window.hide());
+ self.hook(window, "map", () => !title.get() && window.hide());
+ }
+ }}
+ >
+
+);
+
+const MediaPlaying = () => {
+ const players = Players.get_default();
+ const getLabel = (fallback = "") =>
+ players.lastPlayer ? `${players.lastPlayer.title} - ${players.lastPlayer.artist}` : fallback;
+ return (
+ {
+ const label = Variable(getLabel());
+ players.hookLastPlayer(self, ["notify::title", "notify::artist"], () => label.set(getLabel()));
+ setupCustomTooltip(self, bind(label));
+ }}
+ >
+
+ players.hookLastPlayer(self, "notify::entry", () => {
+ const icon = `caelestia-${players.lastPlayer?.entry}-symbolic`;
+ self.icon = players.lastPlayer
+ ? Astal.Icon.lookup_icon(icon)
+ ? icon
+ : "caelestia-media-generic-symbolic"
+ : "caelestia-media-none-symbolic";
+ })
+ }
+ />
+
+ players.hookLastPlayer(self, ["notify::title", "notify::artist"], () => {
+ self.label = getLabel("No media");
+ })
+ }
+ />
+
+ );
+};
+
+const Workspace = ({ idx }: { idx: number }) => {
+ let wsId = Math.floor((hyprland.focusedWorkspace.id - 1) / wsPerGroup) * wsPerGroup + idx;
+ return (
+