summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app.tsx12
-rw-r--r--scss/notifpopups.scss2
-rw-r--r--src/modules/bar.tsx (renamed from modules/bar.tsx)2
-rw-r--r--src/modules/launcher.tsx (renamed from modules/launcher.tsx)5
-rw-r--r--src/modules/notifications.tsx57
-rw-r--r--src/modules/notifpopups.tsx49
-rw-r--r--src/modules/osds.tsx (renamed from modules/osds.tsx)4
-rw-r--r--src/services/apps.ts (renamed from services/apps.ts)0
-rw-r--r--src/services/math.ts (renamed from services/math.ts)0
-rw-r--r--src/services/monitors.ts (renamed from services/monitors.ts)0
-rw-r--r--src/services/players.ts (renamed from services/players.ts)0
-rw-r--r--src/services/updates.ts (renamed from services/updates.ts)2
-rw-r--r--src/utils/icons.ts (renamed from utils/icons.ts)0
-rw-r--r--src/utils/mpris.ts (renamed from utils/mpris.ts)0
-rw-r--r--src/utils/strings.ts (renamed from utils/strings.ts)0
-rw-r--r--src/utils/system.ts (renamed from utils/system.ts)0
-rw-r--r--src/utils/widgets.ts45
-rw-r--r--src/widgets/notification.tsx (renamed from modules/notifpopups.tsx)55
-rw-r--r--src/widgets/popupwindow.ts39
-rw-r--r--utils/widgets.tsx84
20 files changed, 209 insertions, 147 deletions
diff --git a/app.tsx b/app.tsx
index be3231c..31e7ed1 100644
--- a/app.tsx
+++ b/app.tsx
@@ -1,11 +1,11 @@
import { execAsync, GLib, writeFileAsync } from "astal";
import { App } from "astal/gtk3";
-import Bar from "./modules/bar";
-import Launcher from "./modules/launcher";
-import NotifPopups from "./modules/notifpopups";
-import Osds from "./modules/osds";
-import Monitors from "./services/monitors";
-import Players from "./services/players";
+import Bar from "./src/modules/bar";
+import Launcher from "./src/modules/launcher";
+import NotifPopups from "./src/modules/notifpopups";
+import Osds from "./src/modules/osds";
+import Monitors from "./src/services/monitors";
+import Players from "./src/services/players";
const loadStyleAsync = async () => {
if (!GLib.file_test(`${SRC}/scss/scheme/_index.scss`, GLib.FileTest.EXISTS))
diff --git a/scss/notifpopups.scss b/scss/notifpopups.scss
index 325cb7b..25e52e1 100644
--- a/scss/notifpopups.scss
+++ b/scss/notifpopups.scss
@@ -25,7 +25,7 @@
padding-top: lib.s(10);
}
- .popup {
+ .notification {
@include lib.rounded(8, $tr: 0, $br: 0);
@include lib.shadow;
@include font.main;
diff --git a/modules/bar.tsx b/src/modules/bar.tsx
index 5ed309e..b56d94a 100644
--- a/modules/bar.tsx
+++ b/src/modules/bar.tsx
@@ -7,7 +7,7 @@ import AstalNetwork from "gi://AstalNetwork";
import AstalNotifd from "gi://AstalNotifd";
import AstalTray from "gi://AstalTray";
import AstalWp01 from "gi://AstalWp";
-import { bar as config } from "../config";
+import { bar as config } from "../../config";
import type { Monitor } from "../services/monitors";
import Players from "../services/players";
import Updates from "../services/updates";
diff --git a/modules/launcher.tsx b/src/modules/launcher.tsx
index 966cdfd..2fc6eef 100644
--- a/modules/launcher.tsx
+++ b/src/modules/launcher.tsx
@@ -3,12 +3,13 @@ import { Astal, Gtk, Widget } from "astal/gtk3";
import fuzzysort from "fuzzysort";
import type AstalApps from "gi://AstalApps";
import AstalHyprland from "gi://AstalHyprland";
-import { launcher as config } from "../config";
+import { launcher as config } from "../../config";
import { Apps } from "../services/apps";
import Math, { type HistoryItem } from "../services/math";
import { getAppCategoryIcon } from "../utils/icons";
import { launch } from "../utils/system";
-import { PopupWindow, setupCustomTooltip } from "../utils/widgets";
+import { setupCustomTooltip } from "../utils/widgets";
+import PopupWindow from "../widgets/popupwindow";
type Mode = "apps" | "files" | "math";
diff --git a/src/modules/notifications.tsx b/src/modules/notifications.tsx
new file mode 100644
index 0000000..66188a1
--- /dev/null
+++ b/src/modules/notifications.tsx
@@ -0,0 +1,57 @@
+import { Gtk } from "astal/gtk3";
+import AstalNotifd from "gi://AstalNotifd";
+import { PopupWindow, setupChildClickthrough } from "../utils/widgets";
+
+const List = () => (
+ <box
+ vertical
+ valign={Gtk.Align.START}
+ className="list"
+ setup={self => {
+ const notifd = AstalNotifd.get_default();
+ const map = new Map<number, NotifPopup>();
+ self.hook(notifd, "notified", (self, id) => {
+ const notification = notifd.get_notification(id);
+
+ const popup = (<NotifPopup notification={notification} />) as NotifPopup;
+ popup.connect("destroy", () => map.get(notification.id) === popup && map.delete(notification.id));
+ map.get(notification.id)?.destroyWithAnims();
+ map.set(notification.id, popup);
+
+ self.add(
+ <eventbox
+ // Dismiss on middle click
+ onClick={(_, event) => event.button === Astal.MouseButton.MIDDLE && notification.dismiss()}
+ // Close on hover lost
+ onHoverLost={() => popup.destroyWithAnims()}
+ >
+ {popup}
+ </eventbox>
+ );
+
+ // Limit number of popups
+ if (config.maxPopups > 0 && self.children.length > config.maxPopups)
+ map.values().next().value?.destroyWithAnims();
+ });
+ self.hook(notifd, "resolved", (_, id) => map.get(id)?.destroyWithAnims());
+
+ // Change input region to child region so can click through empty space
+ setupChildClickthrough(self);
+ }}
+ />
+);
+
+export default class Notifications extends PopupWindow {
+ constructor() {
+ super({
+ name: "notifications",
+ child: (
+ <box>
+ <List />
+ </box>
+ ),
+ });
+
+ setupChildClickthrough(self);
+ }
+}
diff --git a/src/modules/notifpopups.tsx b/src/modules/notifpopups.tsx
new file mode 100644
index 0000000..5da3092
--- /dev/null
+++ b/src/modules/notifpopups.tsx
@@ -0,0 +1,49 @@
+import { Astal, Gtk } from "astal/gtk3";
+import AstalNotifd from "gi://AstalNotifd";
+import { notifpopups as config } from "../../config";
+import { setupChildClickthrough } from "../utils/widgets";
+import Notification from "../widgets/notification";
+
+export default () => (
+ <window
+ namespace="caelestia-notifpopups"
+ anchor={Astal.WindowAnchor.TOP | Astal.WindowAnchor.RIGHT | Astal.WindowAnchor.BOTTOM}
+ >
+ <box
+ vertical
+ valign={Gtk.Align.START}
+ className="notifpopups"
+ setup={self => {
+ const notifd = AstalNotifd.get_default();
+ const map = new Map<number, Notification>();
+ self.hook(notifd, "notified", (self, id) => {
+ const notification = notifd.get_notification(id);
+
+ const popup = (<Notification popup notification={notification} />) as Notification;
+ popup.connect("destroy", () => map.get(notification.id) === popup && map.delete(notification.id));
+ map.get(notification.id)?.destroyWithAnims();
+ map.set(notification.id, popup);
+
+ self.add(
+ <eventbox
+ // Dismiss on middle click
+ onClick={(_, event) => event.button === Astal.MouseButton.MIDDLE && notification.dismiss()}
+ // Close on hover lost
+ onHoverLost={() => popup.destroyWithAnims()}
+ >
+ {popup}
+ </eventbox>
+ );
+
+ // Limit number of popups
+ if (config.maxPopups > 0 && self.children.length > config.maxPopups)
+ map.values().next().value?.destroyWithAnims();
+ });
+ self.hook(notifd, "resolved", (_, id) => map.get(id)?.destroyWithAnims());
+
+ // Change input region to child region so can click through empty space
+ setupChildClickthrough(self);
+ }}
+ />
+ </window>
+);
diff --git a/modules/osds.tsx b/src/modules/osds.tsx
index b6e1333..a87fcc3 100644
--- a/modules/osds.tsx
+++ b/src/modules/osds.tsx
@@ -5,9 +5,9 @@ import AstalWp from "gi://AstalWp";
import Cairo from "gi://cairo";
import Pango from "gi://Pango";
import PangoCairo from "gi://PangoCairo";
-import { osds as config } from "../config";
+import { osds as config } from "../../config";
import Monitors, { type Monitor } from "../services/monitors";
-import { PopupWindow } from "../utils/widgets";
+import PopupWindow from "../widgets/popupwindow";
const getStyle = (context: Gtk.StyleContext, prop: string) => context.get_property(prop, Gtk.StateFlags.NORMAL);
const getNumStyle = (context: Gtk.StyleContext, prop: string) => getStyle(context, prop) as number;
diff --git a/services/apps.ts b/src/services/apps.ts
index 5396ac7..5396ac7 100644
--- a/services/apps.ts
+++ b/src/services/apps.ts
diff --git a/services/math.ts b/src/services/math.ts
index c66798c..c66798c 100644
--- a/services/math.ts
+++ b/src/services/math.ts
diff --git a/services/monitors.ts b/src/services/monitors.ts
index 78a0161..78a0161 100644
--- a/services/monitors.ts
+++ b/src/services/monitors.ts
diff --git a/services/players.ts b/src/services/players.ts
index b81d4b5..b81d4b5 100644
--- a/services/players.ts
+++ b/src/services/players.ts
diff --git a/services/updates.ts b/src/services/updates.ts
index 0b04e85..5bb6bd1 100644
--- a/services/updates.ts
+++ b/src/services/updates.ts
@@ -1,5 +1,5 @@
import { execAsync, GLib, GObject, property, readFileAsync, register, writeFileAsync } from "astal";
-import { updates as config } from "../config";
+import { updates as config } from "../../config";
interface Update {
name: string;
diff --git a/utils/icons.ts b/src/utils/icons.ts
index f12aee0..f12aee0 100644
--- a/utils/icons.ts
+++ b/src/utils/icons.ts
diff --git a/utils/mpris.ts b/src/utils/mpris.ts
index e0cc111..e0cc111 100644
--- a/utils/mpris.ts
+++ b/src/utils/mpris.ts
diff --git a/utils/strings.ts b/src/utils/strings.ts
index e5bc43e..e5bc43e 100644
--- a/utils/strings.ts
+++ b/src/utils/strings.ts
diff --git a/utils/system.ts b/src/utils/system.ts
index 5d77908..5d77908 100644
--- a/utils/system.ts
+++ b/src/utils/system.ts
diff --git a/src/utils/widgets.ts b/src/utils/widgets.ts
new file mode 100644
index 0000000..08f9740
--- /dev/null
+++ b/src/utils/widgets.ts
@@ -0,0 +1,45 @@
+import { Binding } from "astal";
+import { Astal, Widget } from "astal/gtk3";
+import AstalHyprland from "gi://AstalHyprland";
+
+export const setupCustomTooltip = (self: any, text: string | Binding<string>) => {
+ if (!text) return null;
+
+ const window = new Widget.Window({
+ visible: false,
+ namespace: "caelestia-tooltip",
+ keymode: Astal.Keymode.NONE,
+ exclusivity: Astal.Exclusivity.IGNORE,
+ anchor: Astal.WindowAnchor.TOP | Astal.WindowAnchor.LEFT,
+ child: new Widget.Label({ className: "tooltip", label: text }),
+ });
+ self.set_tooltip_window(window);
+
+ let dirty = true;
+ let lastX = 0;
+ self.connect("size-allocate", () => (dirty = true));
+ window.connect("size-allocate", () => {
+ window.marginLeft = lastX + (self.get_allocated_width() - window.get_preferred_width()[1]) / 2;
+ });
+ if (text instanceof Binding) self.hook(text, (_: any, v: string) => !v && window.hide());
+
+ self.connect("query-tooltip", (_: any, x: number, y: number) => {
+ if (text instanceof Binding && !text.get()) return false;
+ if (dirty) {
+ const { width, height } = self.get_allocation();
+ const { x: cx, y: cy } = AstalHyprland.get_default().get_cursor_position();
+ window.marginLeft = cx + ((width - window.get_preferred_width()[1]) / 2 - x);
+ window.marginTop = cy + (height - y);
+ lastX = cx - x;
+ dirty = false;
+ }
+ return true;
+ });
+
+ self.connect("destroy", () => window.destroy());
+
+ return window;
+};
+
+export const setupChildClickthrough = (self: any) =>
+ self.connect("size-allocate", () => self.get_window()?.set_child_input_shapes());
diff --git a/modules/notifpopups.tsx b/src/widgets/notification.tsx
index c3441a9..0bef5ca 100644
--- a/modules/notifpopups.tsx
+++ b/src/widgets/notification.tsx
@@ -1,9 +1,8 @@
import { GLib, register, timeout } from "astal";
import { Astal, Gtk, Widget } from "astal/gtk3";
import AstalNotifd from "gi://AstalNotifd";
-import { notifpopups as config } from "../config";
+import { notifpopups as config } from "../../config";
import { desktopEntrySubs } from "../utils/icons";
-import { setupChildClickthrough } from "../utils/widgets";
const urgencyToString = (urgency: AstalNotifd.Urgency) => {
switch (urgency) {
@@ -53,17 +52,17 @@ const Image = ({ icon }: { icon: string }) => {
};
@register()
-class NotifPopup extends Widget.Box {
+export default class Notification extends Widget.Box {
readonly #revealer;
#destroyed = false;
- constructor({ notification }: { notification: AstalNotifd.Notification }) {
+ constructor({ notification, popup }: { notification: AstalNotifd.Notification; popup?: boolean }) {
super();
this.#revealer = (
<revealer revealChild transitionType={Gtk.RevealerTransitionType.SLIDE_DOWN} transitionDuration={150}>
<box className="wrapper">
- <box vertical className={`popup ${urgencyToString(notification.urgency)}`}>
+ <box vertical className={`notification ${urgencyToString(notification.urgency)}`}>
<box className="header">
<AppIcon appIcon={notification.appIcon} desktopEntry={notification.appName} />
<label className="app-name" label={notification.appName ?? "Unknown"} />
@@ -106,7 +105,7 @@ class NotifPopup extends Widget.Box {
});
// Close popup after timeout if transient or expire enabled in config
- if (config.expire || notification.transient)
+ if (popup && (config.expire || notification.transient))
timeout(
notification.expireTimeout > 0
? notification.expireTimeout
@@ -131,47 +130,3 @@ class NotifPopup extends Widget.Box {
});
}
}
-
-export default () => (
- <window
- namespace="caelestia-notifpopups"
- anchor={Astal.WindowAnchor.TOP | Astal.WindowAnchor.RIGHT | Astal.WindowAnchor.BOTTOM}
- >
- <box
- vertical
- valign={Gtk.Align.START}
- className="notifpopups"
- setup={self => {
- const notifd = AstalNotifd.get_default();
- const map = new Map<number, NotifPopup>();
- self.hook(notifd, "notified", (self, id) => {
- const notification = notifd.get_notification(id);
-
- const popup = (<NotifPopup notification={notification} />) as NotifPopup;
- popup.connect("destroy", () => map.get(notification.id) === popup && map.delete(notification.id));
- map.get(notification.id)?.destroyWithAnims();
- map.set(notification.id, popup);
-
- self.add(
- <eventbox
- // Dismiss on middle click
- onClick={(_, event) => event.button === Astal.MouseButton.MIDDLE && notification.dismiss()}
- // Close on hover lost
- onHoverLost={() => popup.destroyWithAnims()}
- >
- {popup}
- </eventbox>
- );
-
- // Limit number of popups
- if (config.maxPopups > 0 && self.children.length > config.maxPopups)
- map.values().next().value?.destroyWithAnims();
- });
- self.hook(notifd, "resolved", (_, id) => map.get(id)?.destroyWithAnims());
-
- // Change input region to child region so can click through empty space
- setupChildClickthrough(self);
- }}
- />
- </window>
-);
diff --git a/src/widgets/popupwindow.ts b/src/widgets/popupwindow.ts
new file mode 100644
index 0000000..67aa0ff
--- /dev/null
+++ b/src/widgets/popupwindow.ts
@@ -0,0 +1,39 @@
+import { Binding, register } from "astal";
+import { App, Astal, Gdk, Widget } from "astal/gtk3";
+import AstalHyprland from "gi://AstalHyprland?version=0.1";
+
+const extendProp = <T>(
+ prop: T | Binding<T | undefined> | undefined,
+ override: (prop: T | undefined) => T | undefined
+) => prop && (prop instanceof Binding ? prop.as(override) : override(prop));
+
+@register()
+export default class PopupWindow extends Widget.Window {
+ constructor(props: Widget.WindowProps) {
+ super({
+ keymode: Astal.Keymode.ON_DEMAND,
+ exclusivity: Astal.Exclusivity.IGNORE,
+ ...props,
+ visible: false,
+ application: App,
+ name: props.monitor ? extendProp(props.name, n => (n ? n + props.monitor : undefined)) : props.name,
+ namespace: extendProp(props.name, n => `caelestia-${n}`),
+ onKeyPressEvent: (self, event) => {
+ // Close window on escape
+ if (event.get_keyval()[1] === Gdk.KEY_Escape) self.hide();
+
+ return props.onKeyPressEvent?.(self, event);
+ },
+ borderWidth: 20, // To allow shadow, cause if not it gets cut off
+ });
+ }
+
+ popup_at_widget(widget: JSX.Element, event: Gdk.Event) {
+ const { width, height } = widget.get_allocation();
+ const [_, x, y] = event.get_coords();
+ const { x: cx, y: cy } = AstalHyprland.get_default().get_cursor_position();
+ this.marginLeft = cx + ((width - this.get_preferred_width()[1]) / 2 - x);
+ this.marginTop = cy + (height - y);
+ this.show();
+ }
+}
diff --git a/utils/widgets.tsx b/utils/widgets.tsx
deleted file mode 100644
index 7b16075..0000000
--- a/utils/widgets.tsx
+++ /dev/null
@@ -1,84 +0,0 @@
-import { Binding, register } from "astal";
-import { App, Astal, Gdk, Widget } from "astal/gtk3";
-import AstalHyprland from "gi://AstalHyprland";
-
-export const setupCustomTooltip = (self: any, text: string | Binding<string>) => {
- if (!text) return null;
-
- const window = (
- <window
- visible={false}
- namespace="caelestia-tooltip"
- keymode={Astal.Keymode.NONE}
- exclusivity={Astal.Exclusivity.IGNORE}
- anchor={Astal.WindowAnchor.TOP | Astal.WindowAnchor.LEFT}
- >
- <label className="tooltip" label={text} />
- </window>
- ) as Widget.Window;
- self.set_tooltip_window(window);
-
- let dirty = true;
- let lastX = 0;
- self.connect("size-allocate", () => (dirty = true));
- window.connect("size-allocate", () => {
- window.marginLeft = lastX + (self.get_allocated_width() - window.get_preferred_width()[1]) / 2;
- });
- if (text instanceof Binding) self.hook(text, (_: any, v: string) => !v && window.hide());
-
- self.connect("query-tooltip", (_: any, x: number, y: number) => {
- if (text instanceof Binding && !text.get()) return false;
- if (dirty) {
- const { width, height } = self.get_allocation();
- const { x: cx, y: cy } = AstalHyprland.get_default().get_cursor_position();
- window.marginLeft = cx + ((width - window.get_preferred_width()[1]) / 2 - x);
- window.marginTop = cy + (height - y);
- lastX = cx - x;
- dirty = false;
- }
- return true;
- });
-
- self.connect("destroy", () => window.destroy());
-
- return window;
-};
-
-export const setupChildClickthrough = (self: any) =>
- self.connect("size-allocate", () => self.get_window()?.set_child_input_shapes());
-
-const extendProp = <T,>(
- prop: T | Binding<T | undefined> | undefined,
- override: (prop: T | undefined) => T | undefined
-) => prop && (prop instanceof Binding ? prop.as(override) : override(prop));
-
-@register()
-export class PopupWindow extends Widget.Window {
- constructor(props: Widget.WindowProps) {
- super({
- keymode: Astal.Keymode.ON_DEMAND,
- exclusivity: Astal.Exclusivity.IGNORE,
- ...props,
- visible: false,
- application: App,
- name: props.monitor ? extendProp(props.name, n => (n ? n + props.monitor : undefined)) : props.name,
- namespace: extendProp(props.name, n => `caelestia-${n}`),
- onKeyPressEvent: (self, event) => {
- // Close window on escape
- if (event.get_keyval()[1] === Gdk.KEY_Escape) self.hide();
-
- return props.onKeyPressEvent?.(self, event);
- },
- borderWidth: 20, // To allow shadow, cause if not it gets cut off
- });
- }
-
- popup_at_widget(widget: JSX.Element, event: Gdk.Event) {
- const { width, height } = widget.get_allocation();
- const [_, x, y] = event.get_coords();
- const { x: cx, y: cy } = AstalHyprland.get_default().get_cursor_position();
- this.marginLeft = cx + ((width - this.get_preferred_width()[1]) / 2 - x);
- this.marginTop = cy + (height - y);
- this.show();
- }
-}