chore: make apps-window and floating notifications work better on gtk4

This commit is contained in:
retrozinndev
2025-07-23 21:53:43 -03:00
parent 7f39eb4b46
commit d721ea30db
6 changed files with 91 additions and 72 deletions
+44 -40
View File
@@ -6,10 +6,14 @@ import AstalApps from "gi://AstalApps";
import Pango from "gi://Pango?version=1.0"; import Pango from "gi://Pango?version=1.0";
import { createState, For } from "ags"; import { createState, For } from "ags";
const ignoredKeys = [ const ignoredKeys = [
Gdk.KEY_Right, Gdk.KEY_Right,
Gdk.KEY_Down, Gdk.KEY_Down,
Gdk.KEY_Up, Gdk.KEY_Up,
Gdk.KEY_Shift_L,
Gdk.KEY_Shift_R,
Gdk.KEY_Shift_Lock,
Gdk.KEY_Left, Gdk.KEY_Left,
Gdk.KEY_Return, Gdk.KEY_Return,
Gdk.KEY_space Gdk.KEY_space
@@ -18,55 +22,55 @@ const ignoredKeys = [
export const AppsWindow = (mon: number) => { export const AppsWindow = (mon: number) => {
const [results, setResults] = createState(getApps() as Array<AstalApps.Application>); const [results, setResults] = createState(getApps() as Array<AstalApps.Application>);
const entry: Gtk.SearchEntry = <Gtk.SearchEntry onSearchChanged={(self) => {
setResults(getAstalApps().fuzzy_query(self.text.trim()));
}}/> as Gtk.SearchEntry;
return <PopupWindow namespace="apps-window" layer={Astal.Layer.OVERLAY} return <PopupWindow namespace="apps-window" layer={Astal.Layer.OVERLAY}
exclusivity={Astal.Exclusivity.IGNORE} monitor={mon} marginTop={64} exclusivity={Astal.Exclusivity.IGNORE} monitor={mon} marginTop={64}
cssBackgroundWindow="background={rgba(0, 0, 0, .2)" cssBackgroundWindow="background: rgba(0, 0, 0, .2);"
actionKeyPressed={(_, key) => { actionKeyPressed={(self, key) => {
const entry = self.get_first_child()!.get_first_child()!
.get_first_child()!.get_first_child() as Gtk.SearchEntry;
for(const ignoredKey of ignoredKeys) for(const ignoredKey of ignoredKeys)
if(key === ignoredKey) return if(key === ignoredKey) return
!entry.is_focus && entry.grab_focus(); entry.grab_focus();
}}> }}>
<Gtk.Box class={"apps-window-container"} orientation={Gtk.Orientation.VERTICAL}> <Gtk.Box class={"apps-window-container"} orientation={Gtk.Orientation.VERTICAL} hexpand vexpand>
<Gtk.Box class="apps-area"> <Gtk.SearchEntry onSearchChanged={(self) => {
<Gtk.ScrolledWindow vscrollbarPolicy={Gtk.PolicyType.AUTOMATIC} setResults(getAstalApps().fuzzy_query(self.text.trim()));
hscrollbarPolicy={Gtk.PolicyType.NEVER} overlayScrolling={true} }} />
propagateNaturalHeight={false}> <Gtk.Box class="apps-area" hexpand vexpand>
<Gtk.ScrolledWindow vscrollbarPolicy={Gtk.PolicyType.AUTOMATIC}
hscrollbarPolicy={Gtk.PolicyType.NEVER} overlayScrolling={true}
propagateNaturalHeight={false} hexpand vexpand>
<Gtk.FlowBox rowSpacing={60} columnSpacing={60} <Gtk.FlowBox rowSpacing={60} columnSpacing={60} homogeneous activateOnSingleClick
homogeneous={true} visible={true} minChildrenPerLine={1} minChildrenPerLine={1}>
activateOnSingleClick={true}>
<For each={results}> <For each={results}>
{(app) => {(app) =>
<Gtk.Button visible={true} heightRequest={150} tooltipMarkup={ <Gtk.Button heightRequest={150} tooltipMarkup={`${app.name}${app.description ?
`${app.name}${app.description ? `\n<span foreground="#7f7f7f">${ `\n<span foreground="#7f7f7f">${app.description}</span>`
app.description}</span>` : ""}`.replace(/\&/g, "&amp;")} : ""}`.replace(/\&/g, "&amp;")
} onActivate={() => {
onActivate={() => { execApp(app);
execApp(app); window.close();
window.close(); }} onClicked={() => {
}} onClicked={() => { execApp(app);
execApp(app); window.close();
window.close(); }}>
}}> <Gtk.Box orientation={Gtk.Orientation.VERTICAL} vexpand>
<Gtk.Image iconName={getAppIcon(app) ?? "application-x-executable"}
<Gtk.Box orientation={Gtk.Orientation.VERTICAL}> iconSize={Gtk.IconSize.LARGE} vexpand={false} />
<Gtk.Image iconName={getAppIcon(app) ?? "application-x-executable"} /> <Gtk.Label ellipsize={Pango.EllipsizeMode.END} label={app.name}
<Gtk.Label ellipsize={Pango.EllipsizeMode.END} valign={Gtk.Align.START} valign={Gtk.Align.END} vexpand />
label={app.name || "Unnamed App"} /> </Gtk.Box>
</Gtk.Box> </Gtk.Button>
</Gtk.Button> }
} </For>
</For> </Gtk.FlowBox>
</Gtk.FlowBox> </Gtk.ScrolledWindow>
</Gtk.ScrolledWindow> </Gtk.Box>
</Gtk.Box>
</Gtk.Box> </Gtk.Box>
</PopupWindow> </PopupWindow>
} }
+1
View File
@@ -8,6 +8,7 @@ import { Apps } from "../widget/bar/Apps";
import { Clock } from "../widget/bar/Clock"; import { Clock } from "../widget/bar/Clock";
import { Status } from "../widget/bar/Status"; import { Status } from "../widget/bar/Status";
export const Bar = (mon: number) => { export const Bar = (mon: number) => {
const widgetSpacing = 4; const widgetSpacing = 4;
return <Astal.Window namespace={"top-bar"} layer={Astal.Layer.TOP} return <Astal.Window namespace={"top-bar"} layer={Astal.Layer.TOP}
+2 -2
View File
@@ -7,8 +7,8 @@ import { time } from "../scripts/utils";
import { player } from "../widget/bar/Media"; import { player } from "../widget/bar/Media";
export const CenterWindow = (mon: number) => export const CenterWindow = (mon: number) =>
<PopupWindow namespace={"center-window"} marginTop={10} halign={Gtk.Align.CENTER} <PopupWindow namespace={"center-window"} marginTop={10} monitor={mon}
valign={Gtk.Align.START} monitor={mon}> halign={Gtk.Align.CENTER} valign={Gtk.Align.START}>
<Gtk.Box class={"center-window-container"} spacing={6}> <Gtk.Box class={"center-window-container"} spacing={6}>
<Gtk.Box class={"left"} orientation={Gtk.Orientation.VERTICAL}> <Gtk.Box class={"left"} orientation={Gtk.Orientation.VERTICAL}>
+8 -6
View File
@@ -1,7 +1,9 @@
import { Astal, Gtk } from "ags/gtk4"; import { Astal, Gtk } from "ags/gtk4";
import { PopupWindow } from "../widget/PopupWindow"; import { PopupWindow } from "../widget/PopupWindow";
import { NotifHistory } from "../widget/control-center/NotifHistory";
import { QuickActions } from "../widget/control-center/QuickActions"; import { QuickActions } from "../widget/control-center/QuickActions";
import { NotifHistory } from "../widget/control-center/NotifHistory";
import { Tiles } from "../widget/control-center/Tiles";
import { Sliders } from "../widget/control-center/Sliders";
export const ControlCenter = (mon: number) => export const ControlCenter = (mon: number) =>
@@ -10,13 +12,13 @@ export const ControlCenter = (mon: number) =>
marginTop={10} marginRight={10} marginBottom={10} monitor={mon} marginTop={10} marginRight={10} marginBottom={10} monitor={mon}
widthRequest={395}> widthRequest={395}>
<Gtk.Box orientation={Gtk.Orientation.VERTICAL} <Gtk.Box orientation={Gtk.Orientation.VERTICAL} spacing={16} vexpand={false}>
spacing={16}> <Gtk.Box class={"control-center-container"} vexpand={false}
orientation={Gtk.Orientation.VERTICAL} spacing={12}>
<Gtk.Box class={"control-center-container"}
orientation={Gtk.Orientation.VERTICAL} vexpand={false}>
<QuickActions /> <QuickActions />
<Tiles />
<Sliders />
</Gtk.Box> </Gtk.Box>
<NotifHistory /> <NotifHistory />
</Gtk.Box> </Gtk.Box>
+25 -17
View File
@@ -2,32 +2,40 @@ import { Astal, Gtk } from "ags/gtk4";
import { createBinding, For } from "ags"; import { createBinding, For } from "ags";
import { Notifications } from "../scripts/notifications"; import { Notifications } from "../scripts/notifications";
import { NotificationWidget } from "../widget/Notification"; import { NotificationWidget } from "../widget/Notification";
import { variableToBoolean } from "../scripts/utils";
import AstalNotifd from "gi://AstalNotifd?version=0.1";
import AstalNotifd from "gi://AstalNotifd?version=0.1";
import Adw from "gi://Adw?version=1";
const size = 450;
export const FloatingNotifications = (mon: number) => export const FloatingNotifications = (mon: number) =>
<Astal.Window namespace={"floating-notifications"} monitor={mon} layer={Astal.Layer.OVERLAY} <Astal.Window namespace={"floating-notifications"} monitor={mon} layer={Astal.Layer.OVERLAY}
anchor={Astal.WindowAnchor.TOP | Astal.WindowAnchor.RIGHT} widthRequest={450} anchor={Astal.WindowAnchor.TOP | Astal.WindowAnchor.RIGHT} exclusivity={Astal.Exclusivity.NORMAL}
exclusivity={Astal.Exclusivity.NORMAL}> resizable={false} widthRequest={450}>
<Gtk.Box class={"floating-notifications-container"} <Gtk.Box class={"floating-notifications-container"} spacing={12}
orientation={Gtk.Orientation.VERTICAL} spacing={12} orientation={Gtk.Orientation.VERTICAL}>
visible={variableToBoolean(createBinding(Notifications.getDefault(), "notifications"))}>
<For each={createBinding(Notifications.getDefault(), "notifications")}> <For each={createBinding(Notifications.getDefault(), "notifications")}>
{(notif: AstalNotifd.Notification) => {(notif: AstalNotifd.Notification) =>
<Gtk.Box class={"float-notification"}> <Adw.Clamp maximumSize={size}>
<NotificationWidget notification={notif} showTime={false} <Gtk.Box class={"float-notification"} widthRequest={size} vexpand={false}>
actionClose={() => Notifications.getDefault().removeNotification(notif)}
holdOnHover={true} actionClicked={() => {
const viewAction = notif.actions.filter(action =>
action.label.toLowerCase() === "view")?.[0];
viewAction && notif.invoke(viewAction.id); {/*
}} Why is holdOnHover disabled: the shell for some reason crashes
/> when removing the notification on hover-lost 💔
</Gtk.Box> */}
<NotificationWidget notification={notif} showTime={false}
actionClose={() => Notifications.getDefault().removeNotification(notif)}
holdOnHover={false} actionClicked={() => {
const viewAction = notif.actions.filter(action =>
action.label.toLowerCase() === "view")?.[0];
viewAction && notif.invoke(viewAction.id);
}}
/>
</Gtk.Box>
</Adw.Clamp>
} }
</For> </For>
</Gtk.Box> </Gtk.Box>
+9 -5
View File
@@ -10,6 +10,7 @@ import AstalNotifd from "gi://AstalNotifd";
import Gio from "gi://Gio?version=2.0"; import Gio from "gi://Gio?version=2.0";
import GObject from "gi://GObject?version=2.0"; import GObject from "gi://GObject?version=2.0";
import { time } from "../scripts/utils"; import { time } from "../scripts/utils";
import { onCleanup } from "ags";
const { TOP, LEFT, RIGHT, BOTTOM } = Astal.WindowAnchor; const { TOP, LEFT, RIGHT, BOTTOM } = Astal.WindowAnchor;
@@ -24,10 +25,10 @@ export const LogoutMenu = (mon: number) =>
self.add_controller(controllerKey); self.add_controller(controllerKey);
conns.set(controllerKey, controllerKey.connect("key-released", (_, keyval) => { conns.set(controllerKey, controllerKey.connect("key-released", (_, keyval) => {
if(keyval === Gdk.KEY_Escape) if(keyval === Gdk.KEY_Escape)
self.close(); self.destroy();
})); }));
conns.set(self, self.connect("destroy", () => conns.forEach((id, obj) =>
obj.disconnect(id)))); onCleanup(() => conns.forEach((id, obj) => obj.disconnect(id)));
}}> }}>
<Gtk.Box class={"logout-menu"} orientation={Gtk.Orientation.VERTICAL} <Gtk.Box class={"logout-menu"} orientation={Gtk.Orientation.VERTICAL}
@@ -43,16 +44,19 @@ export const LogoutMenu = (mon: number) =>
return true; return true;
} }
})); }));
onCleanup(() => conns.forEach((id, obj) => obj.disconnect(id)));
}}> }}>
<Gtk.Box class={"top"} hexpand={true} vexpand={false} <Gtk.Box class={"top"} hexpand={true} vexpand={false}
orientation={Gtk.Orientation.VERTICAL}> orientation={Gtk.Orientation.VERTICAL} valign={Gtk.Align.START}>
<Gtk.Label class={"time"} label={time(t => t.format("%H:%M")!)} /> <Gtk.Label class={"time"} label={time(t => t.format("%H:%M")!)} />
<Gtk.Label class={"date"} label={time(d => d.format("%A, %B %d %Y")!)} /> <Gtk.Label class={"date"} label={time(d => d.format("%A, %B %d %Y")!)} />
</Gtk.Box> </Gtk.Box>
<Gtk.Box class={"button-row"} homogeneous={true} heightRequest={360}> <Gtk.Box class={"button-row"} homogeneous={true} heightRequest={360} valign={Gtk.Align.CENTER}
vexpand>
<Gtk.Button class={"poweroff"} iconName={"system-shutdown-symbolic"} <Gtk.Button class={"poweroff"} iconName={"system-shutdown-symbolic"}
onClicked={() => AskPopup(poweroffAsk)} onActivate={() => onClicked={() => AskPopup(poweroffAsk)} onActivate={() =>
AskPopup(poweroffAsk)} AskPopup(poweroffAsk)}