[AGS] GTK4 Migration: Partially complete
This commit is contained in:
@@ -7,209 +7,267 @@
|
||||
*
|
||||
*/
|
||||
|
||||
import { Astal, Gtk, Gdk } from "astal/gtk3"
|
||||
import { Astal, Gdk } from "astal/gtk4"
|
||||
import Notifd from "gi://AstalNotifd";
|
||||
import Notification from "./notifications";
|
||||
import { type Subscribable } from "astal/binding";
|
||||
import { Variable, bind, timeout } from "astal"
|
||||
import { timeout, Variable } from "astal"
|
||||
|
||||
// Config
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
// Config
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
const TIMEOUT_DELAY = 5000;
|
||||
let isRunning = false;
|
||||
let notificationMenuOpen = false;
|
||||
|
||||
class Notifier implements Subscribable {
|
||||
private display: Map<number, Gtk.Widget> = new Map();
|
||||
private notifications: Map<number, Notifd.Notification> = new Map();
|
||||
interface NotificationDetails {
|
||||
notification: Notifd.Notification;
|
||||
backendID: number;
|
||||
notifdID: number;
|
||||
}
|
||||
|
||||
private notifd: Notifd.Notifd;
|
||||
private subscriberData: Variable<Gtk.Widget[]> = Variable( [] );
|
||||
private instanceID: number;
|
||||
private menuOpen: boolean;
|
||||
|
||||
/**
|
||||
* Sets up the notifier
|
||||
*/
|
||||
constructor( id: number ) {
|
||||
this.instanceID = id;
|
||||
this.menuOpen = false;
|
||||
this.notifd = Notifd.get_default();
|
||||
this.notifd.ignoreTimeout = true;
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
// ╭───────────────────────────────────────────────╮
|
||||
// │ Handler │
|
||||
// ╰───────────────────────────────────────────────╯
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
let ShownNotifications: number[] = [];
|
||||
const ShownNotificationsCount: Variable<number> = Variable( 0 );
|
||||
let Notifications: NotificationDetails[] = [];
|
||||
|
||||
this.notifd.connect( 'notified', ( _, id ) => {
|
||||
this.add( id );
|
||||
} );
|
||||
const notifd = Notifd.get_default();
|
||||
notifd.ignoreTimeout = true;
|
||||
|
||||
this.notifd.connect( 'resolved', ( _, id ) => {
|
||||
this.delete( id );
|
||||
} );
|
||||
|
||||
/**
|
||||
* Delete a notification by its internal ID
|
||||
* @param index The notifd ID of the notification
|
||||
*/
|
||||
const deleteNotification = ( index: number ): void => {
|
||||
hideNotification( index );
|
||||
Notifications.splice( index, 1 );
|
||||
}
|
||||
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
/**
|
||||
* Delete a notification by notifd id
|
||||
* @param id The notifd ID of the notification
|
||||
*/
|
||||
const deleteNotificationByNotificationID = ( id: number ): void => {
|
||||
const index = findNotificationByNotificationID( id );
|
||||
if ( index > -1 ) {
|
||||
deleteNotification( index );
|
||||
}
|
||||
}
|
||||
|
||||
private notify () {
|
||||
this.subscriberData.set( [ ...this.display.values() ].reverse() );
|
||||
}
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
|
||||
private add ( id: number ) {
|
||||
this.notifications.set( id, this.notifd.get_notification( id )! );
|
||||
this.show( id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Show an element on screen
|
||||
* @param id - The id of the element to be shown
|
||||
*/
|
||||
private show ( id: number ) {
|
||||
this.set( id, Notification( {
|
||||
notification: this.notifications.get( id )!,
|
||||
onHoverLost: () => {
|
||||
if ( !this.menuOpen ) {
|
||||
this.hide( id );
|
||||
}
|
||||
},
|
||||
setup: () => timeout( TIMEOUT_DELAY, () => {
|
||||
if ( !this.menuOpen ) {
|
||||
this.hide( id );
|
||||
}
|
||||
} ),
|
||||
id: id,
|
||||
delete: deleteHelper,
|
||||
instanceID: this.instanceID
|
||||
} ) )
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a selected widget to be shown
|
||||
* @param id - The id of the element to be referenced for later
|
||||
* @param widget - A GTK widget instance
|
||||
*/
|
||||
private set ( id: number, widget: Gtk.Widget ) {
|
||||
this.display.get( id )?.destroy();
|
||||
this.display.set( id, widget );
|
||||
this.notify();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide, not delete notification (= send to notification centre)
|
||||
* @param id - The id of the notification to hide
|
||||
*/
|
||||
private hide ( id: number ) {
|
||||
this.display.get( id )?.destroy();
|
||||
this.display.delete( id );
|
||||
this.notify();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a notification (from notification centre too)
|
||||
* @param id - The id of the notification to hide
|
||||
*/
|
||||
delete ( id: number ) {
|
||||
this.hide( id );
|
||||
this.notifications.get( id )?.dismiss();
|
||||
this.notifications.delete( id );
|
||||
if ( this.notifications.size == 0 ) {
|
||||
this.menuOpen = false;
|
||||
/**
|
||||
* Find the internal ID from the notifd id for a notification (helper function)
|
||||
* @param id The notifd ID of the notification
|
||||
* @returns The internal ID or -1 if not found
|
||||
*/
|
||||
const findNotificationByNotificationID = ( id: number ): number => {
|
||||
// Find index in Notifications array
|
||||
for (let index = 0; index < Notifications.length; index++) {
|
||||
if ( Notifications[ index ].notifdID === id ) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
openNotificationMenu () {
|
||||
// Show all notifications that have not been cleared
|
||||
if ( this.notifications.size > 0 ) {
|
||||
this.menuOpen = true;
|
||||
this.notifications.forEach( ( _, id ) => {
|
||||
this.show( id );
|
||||
} )
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
hideNotifications () {
|
||||
this.menuOpen = false;
|
||||
this.notifications.forEach( ( _, id ) => {
|
||||
this.hide( id );
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
/**
|
||||
* Add a notification to the notification handler
|
||||
* @param id The notifd ID of the notification
|
||||
*/
|
||||
const addNotification = ( id: number ): void => {
|
||||
print( '[ Notifications ] Notification with id ' + id + ' added.' );
|
||||
const currIndex = Notifications.length;
|
||||
Notifications.push( {
|
||||
notifdID: id,
|
||||
backendID: currIndex,
|
||||
notification: notifd.get_notification( id )
|
||||
} );
|
||||
|
||||
showNotification( currIndex );
|
||||
}
|
||||
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
/**
|
||||
* Start the notifd runner and handle notifications.
|
||||
*/
|
||||
const hookToNotificationDaemon = (): void => {
|
||||
if ( isRunning ) {
|
||||
printerr( '[ Notifications ] Error: Already running' );
|
||||
return;
|
||||
}
|
||||
isRunning = true;
|
||||
|
||||
notifd.connect( 'notified', ( _, id ) => {
|
||||
addNotification( id );
|
||||
} );
|
||||
|
||||
notifd.connect( 'resolved', ( _, id ) => {
|
||||
deleteNotificationByNotificationID( id );
|
||||
} );
|
||||
}
|
||||
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
/**
|
||||
* Show a notification. It will stay on screen (regardless of removeAgain passed in), if
|
||||
* critical urgency
|
||||
* @param id The internal id (index in Notifications array)
|
||||
* @param removeAgain = true If to remove the notification from the screen again automatically
|
||||
*/
|
||||
const showNotification = ( id: number, removeAgain: boolean = true ) => {
|
||||
// Add notification to UI for display
|
||||
ShownNotifications.reverse().push( id );
|
||||
ShownNotifications.reverse();
|
||||
ShownNotificationsCount.set( ShownNotifications.length );
|
||||
|
||||
// Set delay to remove the notification again
|
||||
if ( removeAgain && Notifications[ id ].notification.get_urgency() !== Notifd.Urgency.CRITICAL ) {
|
||||
timeout( TIMEOUT_DELAY, () => {
|
||||
hideNotification( id );
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
toggleNotificationMenu () {
|
||||
if ( this.menuOpen ) {
|
||||
this.hideNotifications();
|
||||
} else {
|
||||
this.openNotificationMenu();
|
||||
}
|
||||
}
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
|
||||
clearAllNotifications () {
|
||||
this.menuOpen = false;
|
||||
this.notifications.forEach( ( _, id ) => {
|
||||
this.delete( id );
|
||||
} )
|
||||
}
|
||||
|
||||
clearNewestNotification () {
|
||||
this.delete( [ ...this.notifications.keys() ][0] );
|
||||
}
|
||||
|
||||
subscribe(callback: (value: unknown) => void): () => void {
|
||||
return this.subscriberData.subscribe( callback );
|
||||
}
|
||||
|
||||
get() {
|
||||
return this.subscriberData.get();
|
||||
/**
|
||||
* Stop displaying notification
|
||||
* @param id The internal id (index in the Notifications array)
|
||||
*/
|
||||
const hideNotification = ( id: number ) => {
|
||||
if ( !notificationMenuOpen ) {
|
||||
ShownNotifications.splice( ShownNotifications.indexOf( id ), 1 );
|
||||
}
|
||||
}
|
||||
|
||||
const notifiers: Map<number, Notifier> = new Map();
|
||||
const deleteHelper = ( id: number, instanceID: number ) => {
|
||||
notifiers.get( instanceID )?.delete( id );
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
/**
|
||||
* Open the notification menu. Called by toggleNotificationMenu too
|
||||
*/
|
||||
const openNotificationMenu = () => {
|
||||
// Simply show all notifications
|
||||
notificationMenuOpen = true;
|
||||
const ShownNotifications = [];
|
||||
for (let index = 0; index < Notifications.length; index++) {
|
||||
ShownNotifications.push( index );
|
||||
}
|
||||
}
|
||||
|
||||
const openNotificationMenu = ( id: number ) => {
|
||||
notifiers.get( id )?.openNotificationMenu();
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
/**
|
||||
* Close the notification menu. Called by toggleNotificationMenu too
|
||||
*/
|
||||
const closeNotificationMenu = () => {
|
||||
// Hide all notifications
|
||||
notificationMenuOpen = true;
|
||||
|
||||
ShownNotifications = [];
|
||||
ShownNotificationsCount.set( 0 );
|
||||
}
|
||||
|
||||
const closeNotificationMenu = ( id: number ) => {
|
||||
notifiers.get( id )?.hideNotifications();
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
/**
|
||||
* Toggle the notification menu (i.e. show all notifications)
|
||||
*/
|
||||
const toggleNotificationMenu = () => {
|
||||
if ( notificationMenuOpen ) {
|
||||
closeNotificationMenu();
|
||||
} else {
|
||||
openNotificationMenu();
|
||||
}
|
||||
}
|
||||
|
||||
const toggleNotificationMenu = ( id: number ) => {
|
||||
notifiers.get( id )?.toggleNotificationMenu();
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
/**
|
||||
* Delete all notifications
|
||||
*/
|
||||
const clearAllNotifications = () => {
|
||||
Notifications = [];
|
||||
ShownNotifications = [];
|
||||
ShownNotificationsCount.set( 0 );
|
||||
}
|
||||
|
||||
const clearAllNotifications = ( id: number ) => {
|
||||
notifiers.get( id )?.clearAllNotifications();
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
/**
|
||||
* Delete the newest notifications
|
||||
*/
|
||||
const clearNewestNotifications = () => {
|
||||
ShownNotifications.splice( 0, 1 );
|
||||
|
||||
Notifications.splice( Notifications.length - 1, 1 );
|
||||
}
|
||||
|
||||
const clearNewestNotifications = ( id: number ) => {
|
||||
notifiers.get( id )?.clearNewestNotification();
|
||||
}
|
||||
|
||||
const startNotificationHandler = (id: number, gdkmonitor: Gdk.Monitor) => {
|
||||
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
// ╭───────────────────────────────────────────────╮
|
||||
// │ User Interface │
|
||||
// ╰───────────────────────────────────────────────╯
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
const startNotificationHandler = (gdkmonitor: Gdk.Monitor) => {
|
||||
const { TOP, RIGHT } = Astal.WindowAnchor
|
||||
const notifier: Notifier = new Notifier( id );
|
||||
notifiers.set( id, notifier );
|
||||
|
||||
hookToNotificationDaemon();
|
||||
const range = (n: number) => [...Array(n).keys()];
|
||||
|
||||
return <window
|
||||
className="NotificationHandler"
|
||||
cssClasses={["NotificationHandler"]}
|
||||
gdkmonitor={gdkmonitor}
|
||||
exclusivity={Astal.Exclusivity.EXCLUSIVE}
|
||||
anchor={TOP | RIGHT}>
|
||||
<box vertical noImplicitDestroy>
|
||||
{bind(notifier)}
|
||||
{ShownNotificationsCount( n => range( n ).map( i => {
|
||||
print( 'Rendering' );
|
||||
// i is index in ShownNotifications array
|
||||
return <Notification id={i} delete={deleteNotification} notification={Notifications[ i ].notification}></Notification>
|
||||
} ) ) }
|
||||
</box>
|
||||
</window>
|
||||
}
|
||||
|
||||
const cliHandler = ( args: string[] ): string => {
|
||||
if ( args[ 1 ] == 'show' ) {
|
||||
openNotificationMenu( 0 );
|
||||
openNotificationMenu();
|
||||
return 'Showing all open notifications';
|
||||
} else if ( args[ 1 ] == 'hide' ) {
|
||||
closeNotificationMenu( 0 );
|
||||
closeNotificationMenu();
|
||||
return 'Hid all notifications';
|
||||
} else if ( args[ 1 ] == 'clear' ) {
|
||||
clearAllNotifications( 0 );
|
||||
clearAllNotifications();
|
||||
return 'Cleared all notifications';
|
||||
} else if ( args[ 1 ] == 'clear-newest' ) {
|
||||
clearNewestNotifications( 0 );
|
||||
clearNewestNotifications();
|
||||
return 'Cleared newest notification';
|
||||
} else if ( args[ 1 ] == 'toggle' ) {
|
||||
toggleNotificationMenu( 0 );
|
||||
toggleNotificationMenu();
|
||||
return 'Toggled notifications';
|
||||
} else {
|
||||
return 'Unknown command!';
|
||||
|
Reference in New Issue
Block a user