notifpopups: fix clicking opening sidebar

This commit is contained in:
2 * r + 2 * t 2025-04-02 19:48:18 +11:00
parent c131a0425e
commit 473e5a6b8a
4 changed files with 59 additions and 44 deletions

View file

@ -15,6 +15,7 @@ import { execAsync, idle, timeout, writeFileAsync } from "astal";
import { App } from "astal/gtk3"; import { App } from "astal/gtk3";
import { style } from "config"; import { style } from "config";
import { initConfig, updateConfig } from "config/funcs"; import { initConfig, updateConfig } from "config/funcs";
import AstalHyprland from "gi://AstalHyprland?version=0.1";
const isLayer = (name: string) => const isLayer = (name: string) =>
["base", "mantle", "crust"].includes(name) || name.startsWith("surface") || name.startsWith("overlay"); ["base", "mantle", "crust"].includes(name) || name.startsWith("surface") || name.startsWith("overlay");
@ -75,9 +76,9 @@ App.start({
Palette.get_default().connect("notify::mode", () => loadStyleAsync().catch(console.error)); Palette.get_default().connect("notify::mode", () => loadStyleAsync().catch(console.error));
<Launcher />; <Launcher />;
<NotifPopups />;
<Osds />; <Osds />;
<Session />; <Session />;
Monitors.get_default().forEach(m => <NotifPopups monitor={m} />);
Monitors.get_default().forEach(m => <SideBar monitor={m} />); Monitors.get_default().forEach(m => <SideBar monitor={m} />);
Monitors.get_default().forEach(m => <Bar monitor={m} />); Monitors.get_default().forEach(m => <Bar monitor={m} />);
Monitors.get_default().forEach(m => <ScreenCorners monitor={m} />); Monitors.get_default().forEach(m => <ScreenCorners monitor={m} />);
@ -99,6 +100,8 @@ App.start({
if (request === "reload-css") loadStyleAsync().catch(console.error); if (request === "reload-css") loadStyleAsync().catch(console.error);
else if (request === "reload-config") updateConfig(); else if (request === "reload-config") updateConfig();
else if (request.startsWith("show")) App.get_window(request.split(" ")[1])?.show(); else if (request.startsWith("show")) App.get_window(request.split(" ")[1])?.show();
else if (request.startsWith("toggle"))
App.toggle_window(request.split(" ")[1] + AstalHyprland.get_default().focusedMonitor.id);
else if (request === "media play-pause") Players.get_default().lastPlayer?.play_pause(); else if (request === "media play-pause") Players.get_default().lastPlayer?.play_pause();
else if (request === "media next") Players.get_default().lastPlayer?.next(); else if (request === "media next") Players.get_default().lastPlayer?.next();
else if (request === "media previous") Players.get_default().lastPlayer?.previous(); else if (request === "media previous") Players.get_default().lastPlayer?.previous();

View file

@ -25,6 +25,10 @@ interface SpacerClassNameProps {
afterSpacer?: boolean; afterSpacer?: boolean;
} }
interface ModuleProps extends SpacerClassNameProps {
monitor: Monitor;
}
const hyprland = AstalHyprland.get_default(); const hyprland = AstalHyprland.get_default();
const getBatteryIcon = (perc: number) => { const getBatteryIcon = (perc: number) => {
@ -70,8 +74,8 @@ const hookFocusedClientProp = (
callback(lastClient); callback(lastClient);
}; };
const switchPane = (name: string) => { const switchPane = (monitor: Monitor, name: string) => {
const sidebar = App.get_window("sidebar") as SideBar | null; const sidebar = App.get_window(`sidebar${monitor.id}`) as SideBar | null;
if (sidebar) { if (sidebar) {
if (sidebar.visible && sidebar.shown.get() === name) sidebar.hide(); if (sidebar.visible && sidebar.shown.get() === name) sidebar.hide();
else sidebar.show(); else sidebar.show();
@ -82,17 +86,17 @@ const switchPane = (name: string) => {
const spacerClassName = ({ beforeSpacer, afterSpacer }: SpacerClassNameProps) => const spacerClassName = ({ beforeSpacer, afterSpacer }: SpacerClassNameProps) =>
`${beforeSpacer ? "before-spacer" : ""} ${afterSpacer ? "after-spacer" : ""}`; `${beforeSpacer ? "before-spacer" : ""} ${afterSpacer ? "after-spacer" : ""}`;
const OSIcon = (props: SpacerClassNameProps) => ( const OSIcon = ({ monitor, ...props }: ModuleProps) => (
<button <button
visible={bind(config.modules.osIcon.enabled)} visible={bind(config.modules.osIcon.enabled)}
className={`module os-icon ${spacerClassName(props)}`} className={`module os-icon ${spacerClassName(props)}`}
onClick={(_, event) => event.button === Astal.MouseButton.PRIMARY && switchPane("dashboard")} onClick={(_, event) => event.button === Astal.MouseButton.PRIMARY && switchPane(monitor, "dashboard")}
> >
{osIcon} {osIcon}
</button> </button>
); );
const ActiveWindow = (props: SpacerClassNameProps) => ( const ActiveWindow = ({ monitor, ...props }: ModuleProps) => (
<box <box
visible={bind(config.modules.activeWindow.enabled)} visible={bind(config.modules.activeWindow.enabled)}
vertical={bind(config.vertical)} vertical={bind(config.vertical)}
@ -134,7 +138,7 @@ const ActiveWindow = (props: SpacerClassNameProps) => (
</box> </box>
); );
const MediaPlaying = (props: SpacerClassNameProps) => { const MediaPlaying = ({ monitor, ...props }: ModuleProps) => {
const players = Players.get_default(); const players = Players.get_default();
const getLabel = (fallback = "") => const getLabel = (fallback = "") =>
players.lastPlayer ? `${players.lastPlayer.title} - ${players.lastPlayer.artist}` : fallback; players.lastPlayer ? `${players.lastPlayer.title} - ${players.lastPlayer.artist}` : fallback;
@ -142,7 +146,7 @@ const MediaPlaying = (props: SpacerClassNameProps) => {
<button <button
visible={bind(config.modules.mediaPlaying.enabled)} visible={bind(config.modules.mediaPlaying.enabled)}
onClick={(_, event) => { onClick={(_, event) => {
if (event.button === Astal.MouseButton.PRIMARY) switchPane("audio"); if (event.button === Astal.MouseButton.PRIMARY) switchPane(monitor, "audio");
else if (event.button === Astal.MouseButton.SECONDARY) players.lastPlayer?.play_pause(); else if (event.button === Astal.MouseButton.SECONDARY) players.lastPlayer?.play_pause();
else if (event.button === Astal.MouseButton.MIDDLE) players.lastPlayer?.raise(); else if (event.button === Astal.MouseButton.MIDDLE) players.lastPlayer?.raise();
}} }}
@ -222,7 +226,7 @@ const Workspace = ({ idx }: { idx: number }) => {
); );
}; };
const Workspaces = (props: SpacerClassNameProps) => ( const Workspaces = ({ monitor, ...props }: ModuleProps) => (
<eventbox <eventbox
visible={bind(config.modules.workspaces.enabled)} visible={bind(config.modules.workspaces.enabled)}
onScroll={(_, event) => { onScroll={(_, event) => {
@ -253,7 +257,7 @@ const TrayItem = (item: AstalTray.TrayItem) => (
</menubutton> </menubutton>
); );
const Tray = (props: SpacerClassNameProps) => { const Tray = ({ monitor, ...props }: ModuleProps) => {
const visible = Variable.derive( const visible = Variable.derive(
[config.modules.tray.enabled, bind(AstalTray.get_default(), "items")], [config.modules.tray.enabled, bind(AstalTray.get_default(), "items")],
(e, i) => e && i.length > 0 (e, i) => e && i.length > 0
@ -275,7 +279,7 @@ const Network = () => (
<button <button
onClick={(_, event) => { onClick={(_, event) => {
const network = AstalNetwork.get_default(); const network = AstalNetwork.get_default();
if (event.button === Astal.MouseButton.PRIMARY) switchPane("connectivity"); if (event.button === Astal.MouseButton.PRIMARY) switchPane(monitor, "connectivity");
else if (event.button === Astal.MouseButton.SECONDARY) network.wifi.enabled = !network.wifi.enabled; else if (event.button === Astal.MouseButton.SECONDARY) network.wifi.enabled = !network.wifi.enabled;
else if (event.button === Astal.MouseButton.MIDDLE) else if (event.button === Astal.MouseButton.MIDDLE)
execAsync("uwsm app -- gnome-control-center wifi").catch(() => { execAsync("uwsm app -- gnome-control-center wifi").catch(() => {
@ -378,7 +382,7 @@ const BluetoothDevice = (device: AstalBluetooth.Device) => (
<button <button
visible={bind(device, "connected")} visible={bind(device, "connected")}
onClick={(_, event) => { onClick={(_, event) => {
if (event.button === Astal.MouseButton.PRIMARY) switchPane("connectivity"); if (event.button === Astal.MouseButton.PRIMARY) switchPane(monitor, "connectivity");
else if (event.button === Astal.MouseButton.SECONDARY) else if (event.button === Astal.MouseButton.SECONDARY)
device.disconnect_device((_, res) => device.disconnect_device_finish(res)); device.disconnect_device((_, res) => device.disconnect_device_finish(res));
else if (event.button === Astal.MouseButton.MIDDLE) else if (event.button === Astal.MouseButton.MIDDLE)
@ -398,7 +402,7 @@ const Bluetooth = () => (
<box vertical={bind(config.vertical)} className="bluetooth"> <box vertical={bind(config.vertical)} className="bluetooth">
<button <button
onClick={(_, event) => { onClick={(_, event) => {
if (event.button === Astal.MouseButton.PRIMARY) switchPane("connectivity"); if (event.button === Astal.MouseButton.PRIMARY) switchPane(monitor, "connectivity");
else if (event.button === Astal.MouseButton.SECONDARY) AstalBluetooth.get_default().toggle(); else if (event.button === Astal.MouseButton.SECONDARY) AstalBluetooth.get_default().toggle();
else if (event.button === Astal.MouseButton.MIDDLE) else if (event.button === Astal.MouseButton.MIDDLE)
execAsync("uwsm app -- blueman-manager").catch(console.error); execAsync("uwsm app -- blueman-manager").catch(console.error);
@ -440,7 +444,7 @@ const Bluetooth = () => (
</box> </box>
); );
const StatusIcons = (props: SpacerClassNameProps) => ( const StatusIcons = ({ monitor, ...props }: ModuleProps) => (
<box <box
visible={bind(config.modules.statusIcons.enabled)} visible={bind(config.modules.statusIcons.enabled)}
vertical={bind(config.vertical)} vertical={bind(config.vertical)}
@ -451,10 +455,10 @@ const StatusIcons = (props: SpacerClassNameProps) => (
</box> </box>
); );
const PkgUpdates = (props: SpacerClassNameProps) => ( const PkgUpdates = ({ monitor, ...props }: ModuleProps) => (
<button <button
visible={bind(config.modules.pkgUpdates.enabled)} visible={bind(config.modules.pkgUpdates.enabled)}
onClick={(_, event) => event.button === Astal.MouseButton.PRIMARY && switchPane("packages")} onClick={(_, event) => event.button === Astal.MouseButton.PRIMARY && switchPane(monitor, "packages")}
setup={self => setup={self =>
setupCustomTooltip( setupCustomTooltip(
self, self,
@ -469,10 +473,10 @@ const PkgUpdates = (props: SpacerClassNameProps) => (
</button> </button>
); );
const NotifCount = (props: SpacerClassNameProps) => ( const NotifCount = ({ monitor, ...props }: ModuleProps) => (
<button <button
visible={bind(config.modules.notifCount.enabled)} visible={bind(config.modules.notifCount.enabled)}
onClick={(_, event) => event.button === Astal.MouseButton.PRIMARY && switchPane("notifpane")} onClick={(_, event) => event.button === Astal.MouseButton.PRIMARY && switchPane(monitor, "notifpane")}
setup={self => setup={self =>
setupCustomTooltip( setupCustomTooltip(
self, self,
@ -500,7 +504,7 @@ const NotifCount = (props: SpacerClassNameProps) => (
</button> </button>
); );
const Battery = (props: SpacerClassNameProps) => { const Battery = ({ monitor, ...props }: ModuleProps) => {
const visible = Variable.derive( const visible = Variable.derive(
[config.modules.battery.enabled, bind(AstalBattery.get_default(), "isBattery")], [config.modules.battery.enabled, bind(AstalBattery.get_default(), "isBattery")],
(e, i) => e && i (e, i) => e && i
@ -553,10 +557,10 @@ const DateTimeVertical = (props: SpacerClassNameProps) => (
</box> </box>
); );
const DateTime = (props: SpacerClassNameProps) => ( const DateTime = ({ monitor, ...props }: ModuleProps) => (
<button <button
visible={bind(config.modules.dateTime.enabled)} visible={bind(config.modules.dateTime.enabled)}
onClick={(_, event) => event.button === Astal.MouseButton.PRIMARY && switchPane("time")} onClick={(_, event) => event.button === Astal.MouseButton.PRIMARY && switchPane(monitor, "time")}
setup={self => setup={self =>
setupCustomTooltip(self, bindCurrentTime(bind(config.modules.dateTime.detailedFormat), undefined, self)) setupCustomTooltip(self, bindCurrentTime(bind(config.modules.dateTime.detailedFormat), undefined, self))
} }
@ -565,7 +569,7 @@ const DateTime = (props: SpacerClassNameProps) => (
</button> </button>
); );
const Power = (props: SpacerClassNameProps) => ( const Power = ({ monitor, ...props }: ModuleProps) => (
<button <button
visible={bind(config.modules.power.enabled)} visible={bind(config.modules.power.enabled)}
className={`module power ${spacerClassName(props)}`} className={`module power ${spacerClassName(props)}`}
@ -609,21 +613,21 @@ const Bar = ({ monitor }: { monitor: Monitor }) => {
return ( return (
<centerbox vertical={bind(config.vertical)} className={bind(className)} onDestroy={() => className.drop()}> <centerbox vertical={bind(config.vertical)} className={bind(className)} onDestroy={() => className.drop()}>
<box vertical={bind(config.vertical)}> <box vertical={bind(config.vertical)}>
<OSIcon /> <OSIcon monitor={monitor} />
<ActiveWindow /> <ActiveWindow monitor={monitor} />
<MediaPlaying beforeSpacer /> <MediaPlaying monitor={monitor} beforeSpacer />
<BrightnessSpacer monitor={monitor} /> <BrightnessSpacer monitor={monitor} />
</box> </box>
<Workspaces beforeSpacer afterSpacer /> <Workspaces monitor={monitor} beforeSpacer afterSpacer />
<box vertical={bind(config.vertical)}> <box vertical={bind(config.vertical)}>
<VolumeSpacer /> <VolumeSpacer />
<Tray afterSpacer /> <Tray monitor={monitor} afterSpacer />
<StatusIcons /> <StatusIcons monitor={monitor} />
<PkgUpdates /> <PkgUpdates monitor={monitor} />
<NotifCount /> <NotifCount monitor={monitor} />
<Battery /> <Battery monitor={monitor} />
<DateTime /> <DateTime monitor={monitor} />
<Power /> <Power monitor={monitor} />
</box> </box>
</centerbox> </centerbox>
); );

View file

@ -1,11 +1,15 @@
import type { Monitor } from "@/services/monitors";
import { idle, timeout } from "astal";
import { App, Astal, Gtk } from "astal/gtk3"; import { App, Astal, Gtk } from "astal/gtk3";
import { notifpopups as config } from "config"; import { notifpopups as config } from "config";
import AstalNotifd from "gi://AstalNotifd"; import AstalNotifd from "gi://AstalNotifd";
import { setupChildClickthrough } from "../utils/widgets"; import { setupChildClickthrough } from "../utils/widgets";
import Notification from "../widgets/notification"; import Notification from "../widgets/notification";
import type SideBar from "./sidebar";
export default () => ( export default ({ monitor }: { monitor: Monitor }) => (
<window <window
monitor={monitor.id}
namespace="caelestia-notifpopups" namespace="caelestia-notifpopups"
anchor={Astal.WindowAnchor.TOP | Astal.WindowAnchor.RIGHT | Astal.WindowAnchor.BOTTOM} anchor={Astal.WindowAnchor.TOP | Astal.WindowAnchor.RIGHT | Astal.WindowAnchor.BOTTOM}
> >
@ -16,10 +20,9 @@ export default () => (
setup={self => { setup={self => {
const notifd = AstalNotifd.get_default(); const notifd = AstalNotifd.get_default();
const map = new Map<number, Notification>(); const map = new Map<number, Notification>();
let notifsOpen = false;
self.hook(notifd, "notified", (self, id) => { self.hook(notifd, "notified", (self, id) => {
if (notifsOpen || notifd.dontDisturb) return; if (notifd.dontDisturb) return;
const notification = notifd.get_notification(id); const notification = notifd.get_notification(id);
@ -35,7 +38,11 @@ export default () => (
if (event.button === Astal.MouseButton.PRIMARY) { if (event.button === Astal.MouseButton.PRIMARY) {
if (notification.actions.length === 1) if (notification.actions.length === 1)
notification.invoke(notification.actions[0].id); notification.invoke(notification.actions[0].id);
else App.get_window("notifications")?.show(); else {
sidebar?.shown.set("notifpane");
sidebar?.show();
popup.destroyWithAnims();
}
} }
// Dismiss on middle click // Dismiss on middle click
else if (event.button === Astal.MouseButton.MIDDLE) notification.dismiss(); else if (event.button === Astal.MouseButton.MIDDLE) notification.dismiss();
@ -54,12 +61,13 @@ export default () => (
}); });
self.hook(notifd, "resolved", (_, id) => map.get(id)?.destroyWithAnims()); self.hook(notifd, "resolved", (_, id) => map.get(id)?.destroyWithAnims());
self.hook(App, "window-toggled", (_, window) => { let sidebar: SideBar | null;
if (window.name === "notifications") {
notifsOpen = window.visible; const awaitSidebar = () => {
map.forEach(n => n.destroyWithAnims()); sidebar = App.get_window(`sidebar${monitor.id}`) as SideBar | null;
} if (!sidebar) timeout(100, awaitSidebar);
}); };
idle(awaitSidebar);
// Change input region to child region so can click through empty space // Change input region to child region so can click through empty space
setupChildClickthrough(self); setupChildClickthrough(self);

View file

@ -16,7 +16,7 @@ export default class SideBar extends Widget.Window {
constructor({ monitor }: { monitor: Monitor }) { constructor({ monitor }: { monitor: Monitor }) {
super({ super({
application: App, application: App,
name: "sidebar", name: `sidebar${monitor.id}`,
namespace: "caelestia-sidebar", namespace: "caelestia-sidebar",
monitor: monitor.id, monitor: monitor.id,
anchor: Astal.WindowAnchor.LEFT | Astal.WindowAnchor.TOP | Astal.WindowAnchor.BOTTOM, anchor: Astal.WindowAnchor.LEFT | Astal.WindowAnchor.TOP | Astal.WindowAnchor.BOTTOM,