feat(agsV2): add notif-button in bar
All checks were successful
Discord / discord commits (push) Has been skipped

This commit is contained in:
matt1432 2024-10-16 22:33:15 -04:00
parent e3ca8dc85e
commit 50fc642f03
11 changed files with 228 additions and 97 deletions

View file

@ -5,7 +5,7 @@ import style from './style.scss';
import Bar from './widgets/bar/wim'; import Bar from './widgets/bar/wim';
import BgFade from './widgets/bg-fade/main'; import BgFade from './widgets/bg-fade/main';
import Corners from './widgets/corners/main'; import Corners from './widgets/corners/main';
import { NotifPopups } from './widgets/notifs/main'; import { NotifPopups, NotifCenter } from './widgets/notifs/main';
import MonitorClicks from './services/monitor-clicks'; import MonitorClicks from './services/monitor-clicks';
@ -18,6 +18,7 @@ App.start({
BgFade(); BgFade();
Corners(); Corners();
NotifPopups(); NotifPopups();
NotifCenter();
new MonitorClicks(); new MonitorClicks();
}, },

View file

@ -113,26 +113,27 @@ export default class MonitorClicks extends GObject.Object {
return; return;
} }
const layers = JSON.parse(await hyprMessage('j/layers')) as LayerResult; try {
const pos = JSON.parse(await hyprMessage('j/cursorpos')) as CursorPos; const layers = JSON.parse(await hyprMessage('j/layers')) as LayerResult;
const pos = JSON.parse(await hyprMessage('j/cursorpos')) as CursorPos;
Object.values(layers).forEach((key) => { Object.values(layers).forEach((key) => {
const overlayLayer = key.levels['3']; const overlayLayer = key.levels['3'];
if (overlayLayer) { if (overlayLayer) {
const noCloseWidgetsNames = [ const noCloseWidgetsNames = [
'bar-', 'bar-',
'osk', 'osk',
]; ];
const getNoCloseWidgets = (names: string[]) => { const getNoCloseWidgets = (names: string[]) => {
const arr = [] as Layer[]; const arr = [] as Layer[];
names.forEach((name) => { names.forEach((name) => {
arr.push( arr.push(
overlayLayer.find( overlayLayer.find(
(n) => n.namespace.startsWith(name), (n) => n.namespace.startsWith(name),
) || ) ||
// Return an empty Layer if widget doesn't exist // Return an empty Layer if widget doesn't exist
{ {
address: '', address: '',
@ -142,44 +143,48 @@ export default class MonitorClicks extends GObject.Object {
h: 0, h: 0,
namespace: '', namespace: '',
}, },
); );
}); });
return arr; return arr;
}; };
const clickIsOnWidget = (w: Layer) => { const clickIsOnWidget = (w: Layer) => {
return pos.x > w.x && pos.x < w.x + w.w && return pos.x > w.x && pos.x < w.x + w.w &&
pos.y > w.y && pos.y < w.y + w.h; pos.y > w.y && pos.y < w.y + w.h;
}; };
const noCloseWidgets = getNoCloseWidgets(noCloseWidgetsNames); const noCloseWidgets = getNoCloseWidgets(noCloseWidgetsNames);
const widgets = overlayLayer.filter((n) => { const widgets = overlayLayer.filter((n) => {
let window = null as null | PopupWindow; let window = null as null | PopupWindow;
if (App.get_windows().some((win) => if (App.get_windows().some((win) =>
win.name === n.namespace)) { win.name === n.namespace)) {
window = (App.get_window(n.namespace) as PopupWindow); window = (App.get_window(n.namespace) as PopupWindow);
} }
return window && return window &&
window.close_on_unfocus && window.close_on_unfocus &&
window.close_on_unfocus === window.close_on_unfocus ===
clickStage; clickStage;
});
if (noCloseWidgets.some(clickIsOnWidget)) {
// Don't handle clicks when on certain widgets
}
else {
widgets.forEach((w) => {
if (!(pos.x > w.x && pos.x < w.x + w.w &&
pos.y > w.y && pos.y < w.y + w.h)) {
App.get_window(w.namespace)?.set_visible(false);
}
}); });
if (noCloseWidgets.some(clickIsOnWidget)) {
// Don't handle clicks when on certain widgets
}
else {
widgets.forEach((w) => {
if (!(pos.x > w.x && pos.x < w.x + w.w &&
pos.y > w.y && pos.y < w.y + w.h)) {
App.get_window(w.namespace)?.set_visible(false);
}
});
}
} }
} });
}); }
catch (e) {
console.log(e);
}
} }
} }

View file

@ -20,33 +20,38 @@ Hyprland.connect('event', async() => {
m1.size === m2.size && m1.size === m2.size &&
Array.from(m1.keys()).every((key) => m1.get(key) === m2.get(key)); Array.from(m1.keys()).every((key) => m1.get(key) === m2.get(key));
const newMonitors = JSON.parse(await hyprMessage('j/monitors')) as AstalHyprland.Monitor[]; try {
const newMonitors = JSON.parse(await hyprMessage('j/monitors')) as AstalHyprland.Monitor[];
const fs = FullscreenState.get(); const fs = FullscreenState.get();
const fsClients = Hyprland.get_clients().filter((c) => { const fsClients = Hyprland.get_clients().filter((c) => {
const mon = newMonitors.find((monitor) => monitor.id === c.get_monitor().id); const mon = newMonitors.find((monitor) => monitor.id === c.get_monitor()?.id);
return c.fullscreenClient !== 0 && return c.fullscreenClient !== 0 &&
c.workspace.id === mon?.activeWorkspace.id; c.workspace.id === mon?.activeWorkspace.id;
});
const monitors = fsClients.map((c) =>
get_monitor_desc(c.monitor));
const clientAddrs = new Map(fsClients.map((c) => [
get_monitor_desc(c.monitor),
c.address ?? '',
]));
const hasChanged =
!arrayEquals(monitors, fs.monitors) ||
!mapEquals(clientAddrs, fs.clientAddrs);
if (hasChanged) {
FullscreenState.set({
monitors,
clientAddrs,
}); });
const monitors = fsClients.map((c) =>
get_monitor_desc(c.monitor));
const clientAddrs = new Map(fsClients.map((c) => [
get_monitor_desc(c.monitor),
c.address ?? '',
]));
const hasChanged =
!arrayEquals(monitors, fs.monitors) ||
!mapEquals(clientAddrs, fs.clientAddrs);
if (hasChanged) {
FullscreenState.set({
monitors,
clientAddrs,
});
}
}
catch (e) {
console.log(e);
} }
}); });

View file

@ -39,7 +39,12 @@ export default () => {
Hyprland.connect('notify::focused-client', () => updateVars()); Hyprland.connect('notify::focused-client', () => updateVars());
Hyprland.connect('client-removed', () => updateVars()); Hyprland.connect('client-removed', () => updateVars());
Hyprland.connect('client-added', async() => { Hyprland.connect('client-added', async() => {
updateVars(Hyprland.get_client(JSON.parse(await hyprMessage('j/activewindow')).address)); try {
updateVars(Hyprland.get_client(JSON.parse(await hyprMessage('j/activewindow')).address));
}
catch (e) {
console.log(e);
}
}); });
return ( return (

View file

@ -0,0 +1,59 @@
import { bind } from 'astal';
import { App } from 'astal/gtk3';
import AstalNotifd from 'gi://AstalNotifd?version=0.1';
const Notifications = AstalNotifd.get_default();
import Separator from '../../misc/separator';
const SPACING = 4;
// Types
import { PopupWindow } from '../../misc/popup-window';
export default () => (
<button
className="bar-item"
cursor="pointer"
onButtonReleaseEvent={(self) => {
const win = App.get_window('win-notif-center') as PopupWindow;
win.set_x_pos(
self.get_allocation(),
'right',
);
win.visible = !win.visible;
}}
setup={(self) => {
App.connect('window-toggled', (_, win) => {
if (win.name === 'win-notif-center') {
self.toggleClassName('toggle-on', win.visible);
}
});
}}
>
<box>
<icon
icon={bind(Notifications, 'notifications').as((notifs) => {
if (Notifications.dontDisturb) {
return 'notification-disabled-symbolic';
}
else if (notifs.length > 0) {
return 'notification-new-symbolic';
}
else {
return 'notification-symbolic';
}
})}
/>
<Separator size={SPACING} />
<label label={bind(Notifications, 'notifications').as((n) => String(n.length))} />
</box>
</button>
);

View file

@ -19,7 +19,7 @@ const Workspace = ({ id = 0 }) => (
tooltip_text={id.toString()} tooltip_text={id.toString()}
onClickRelease={() => { onClickRelease={() => {
hyprMessage(`dispatch workspace ${id}`); hyprMessage(`dispatch workspace ${id}`).catch(console.log);
}} }}
> >
<box <box

View file

@ -8,6 +8,12 @@
font-size: 20px; font-size: 20px;
min-height: 35px; min-height: 35px;
transition: background-color 300ms;
&:hover {
background-color: lighten($window_bg_color, 3%);
}
&.battery icon { &.battery icon {
&.charging { &.charging {
color: green; color: green;

View file

@ -3,6 +3,7 @@ import { Astal, Gtk } from 'astal/gtk3';
import Battery from './items/battery'; import Battery from './items/battery';
import Clock from './items/clock'; import Clock from './items/clock';
import CurrentClient from './items/current-client'; import CurrentClient from './items/current-client';
import NotifButton from './items/notif-button';
import SysTray from './items/tray'; import SysTray from './items/tray';
import Workspaces from './items/workspaces'; import Workspaces from './items/workspaces';
@ -39,6 +40,10 @@ export default () => (
</box> </box>
<box hexpand halign={Gtk.Align.END}> <box hexpand halign={Gtk.Align.END}>
<NotifButton />
<Separator size={8} />
<Battery /> <Battery />
<Separator size={2} /> <Separator size={2} />

View file

@ -1,8 +1,8 @@
import { App, Astal, Widget } from 'astal/gtk3'; import { App, Astal, Gtk, Widget } from 'astal/gtk3';
import { register, property } from 'astal/gobject'; import { register, property } from 'astal/gobject';
import { Binding, idle } from 'astal'; import { Binding, idle } from 'astal';
import { hyprMessage } from '../../lib'; import { get_hyprland_monitor, hyprMessage } from '../../lib';
/* Types */ /* Types */
type CloseType = 'none' | 'stay' | 'released' | 'clicked'; type CloseType = 'none' | 'stay' | 'released' | 'clicked';
@ -60,7 +60,7 @@ export class PopupWindow extends Widget.Window {
App.add_window(this); App.add_window(this);
const setTransition = (_: PopupWindow, t: HyprTransition | Binding<HyprTransition>) => { const setTransition = (_: PopupWindow, t: HyprTransition | Binding<HyprTransition>) => {
hyprMessage(`keyword layerrule animation ${t}, ${this.name}`); hyprMessage(`keyword layerrule animation ${t}, ${this.name}`).catch(console.log);
}; };
this.connect('notify::transition', setTransition); this.connect('notify::transition', setTransition);
@ -82,6 +82,34 @@ export class PopupWindow extends Widget.Window {
} }
}); });
}; };
set_x_pos(
alloc: Gtk.Allocation,
side = 'right' as 'left' | 'right',
) {
const monitor = this.gdkmonitor ??
this.get_display().get_monitor_at_point(alloc.x, alloc.y);
// @ts-expect-error this should exist
const transform = get_hyprland_monitor(monitor)?.transform;
let width: number;
if (transform && (transform === 1 || transform === 3)) {
width = monitor.get_geometry().height;
}
else {
width = monitor.get_geometry().width;
}
this.margin_right = side === 'right' ?
(width - alloc.x - alloc.width) :
this.margin_right;
this.margin_left = side === 'right' ?
this.margin_left :
(alloc.x - alloc.width);
}
} }
export default (props: PopupWindowProps) => new PopupWindow(props); export default (props: PopupWindowProps) => new PopupWindow(props);

View file

@ -63,41 +63,47 @@ export class NotifGestureWrapper extends Widget.EventBox {
public dragging: boolean; public dragging: boolean;
private async get_hovered(): Promise<boolean> { private async get_hovered(): Promise<boolean> {
const layers = JSON.parse(await hyprMessage('j/layers')) as LayerResult; try {
const cursorPos = JSON.parse(await hyprMessage('j/cursorpos')) as CursorPos; const layers = JSON.parse(await hyprMessage('j/layers')) as LayerResult;
const cursorPos = JSON.parse(await hyprMessage('j/cursorpos')) as CursorPos;
const window = this.get_window(); const window = this.get_window();
if (window) { if (window) {
const monitor = display?.get_monitor_at_window(window); const monitor = display?.get_monitor_at_window(window);
if (monitor) { if (monitor) {
const plugName = get_hyprland_monitor(monitor)?.name; const plugName = get_hyprland_monitor(monitor)?.name;
const notifLayer = layers[plugName ?? '']?.levels['3'] const notifLayer = layers[plugName ?? '']?.levels['3']
?.find((n) => n.namespace === 'notifications'); ?.find((n) => n.namespace === 'notifications');
if (notifLayer) { if (notifLayer) {
const index = [...NotifGestureWrapper.popups.keys()] const index = [...NotifGestureWrapper.popups.keys()]
.sort((a, b) => b - a) .sort((a, b) => b - a)
.indexOf(this.id); .indexOf(this.id);
const popups = [...NotifGestureWrapper.popups.entries()] const popups = [...NotifGestureWrapper.popups.entries()]
.sort((a, b) => b[0] - a[0]) .sort((a, b) => b[0] - a[0])
.map(([key, val]) => [key, val.get_allocated_height()]); .map(([key, val]) => [key, val.get_allocated_height()]);
const thisY = notifLayer.y + popups const thisY = notifLayer.y + popups
.map((v) => v[1]) .map((v) => v[1])
.slice(0, index) .slice(0, index)
.reduce((prev, curr) => prev + curr, 0); .reduce((prev, curr) => prev + curr, 0);
if (cursorPos.y >= thisY && cursorPos.y <= thisY + (popups[index][1] ?? 0)) { if (cursorPos.y >= thisY && cursorPos.y <= thisY + (popups[index][1] ?? 0)) {
if (cursorPos.x >= notifLayer.x && cursorPos.x <= notifLayer.x + notifLayer.w) { if (cursorPos.x >= notifLayer.x &&
return true; cursorPos.x <= notifLayer.x + notifLayer.w) {
return true;
}
} }
} }
} }
} }
} }
catch (e) {
console.log(e);
}
return false; return false;
} }

View file

@ -1,5 +1,7 @@
import { Astal } from 'astal/gtk3'; import { Astal } from 'astal/gtk3';
import PopupWindow from '../misc/popup-window';
import Popups from './popups'; import Popups from './popups';
@ -13,3 +15,12 @@ export const NotifPopups = () => (
<Popups /> <Popups />
</window> </window>
); );
export const NotifCenter = () => (
<PopupWindow
name="notif-center"
anchor={Astal.WindowAnchor.TOP | Astal.WindowAnchor.RIGHT}
>
<box css="min-height: 100px; min-width: 100px; background: black;" />
</PopupWindow>
);