300 lines
11 KiB
TypeScript
300 lines
11 KiB
TypeScript
/*
|
|
* dotfiles - handler.ts
|
|
*
|
|
* Created by Janis Hutz 03/21/2025, Licensed under the GPL V3 License
|
|
* https://janishutz.com, development@janishutz.com
|
|
*
|
|
*
|
|
*/
|
|
|
|
import { App, Astal, Gdk } from "astal/gtk4"
|
|
import Notifd from "gi://AstalNotifd";
|
|
import Notification from "./notifications";
|
|
import { timeout, Variable } from "astal"
|
|
|
|
// ───────────────────────────────────────────────────────────────────
|
|
// Config
|
|
// ───────────────────────────────────────────────────────────────────
|
|
const TIMEOUT_DELAY = 5000;
|
|
let isRunning = false;
|
|
let notificationMenuOpen = false;
|
|
|
|
interface NotificationDetails {
|
|
notification: Notifd.Notification;
|
|
backendID: number;
|
|
notifdID: number;
|
|
}
|
|
|
|
|
|
// ───────────────────────────────────────────────────────────────────
|
|
// ╭───────────────────────────────────────────────╮
|
|
// │ Handler │
|
|
// ╰───────────────────────────────────────────────╯
|
|
// ───────────────────────────────────────────────────────────────────
|
|
let ShownNotifications: Variable<number[]> = Variable( [] );
|
|
let Notifications: NotificationDetails[] = [];
|
|
|
|
const notifd = Notifd.get_default();
|
|
notifd.ignoreTimeout = true;
|
|
|
|
|
|
/**
|
|
* 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 );
|
|
if ( Notifications.length === 0 ) {
|
|
notificationMenuOpen = false;
|
|
}
|
|
}
|
|
|
|
// ───────────────────────────────────────────────────────────────────
|
|
|
|
|
|
/**
|
|
* 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 );
|
|
}
|
|
}
|
|
|
|
// ───────────────────────────────────────────────────────────────────
|
|
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
// ───────────────────────────────────────────────────────────────────
|
|
|
|
|
|
/**
|
|
* Add a notification to the notification handler
|
|
* @param id The notifd ID of the notification
|
|
*/
|
|
const addNotification = ( id: number ): void => {
|
|
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
|
|
const not = [...ShownNotifications.get()].reverse();
|
|
not.push( id );
|
|
ShownNotifications.set( not.reverse() );
|
|
|
|
// Set delay to remove the notification again
|
|
if ( removeAgain && Notifications[ id ].notification.get_urgency() !== Notifd.Urgency.CRITICAL ) {
|
|
timeout( TIMEOUT_DELAY, () => {
|
|
hideNotification( id );
|
|
} );
|
|
}
|
|
}
|
|
|
|
// ───────────────────────────────────────────────────────────────────
|
|
|
|
|
|
/**
|
|
* Stop displaying notification
|
|
* @param id The internal id (index in the Notifications array)
|
|
*/
|
|
const hideNotification = ( id: number ) => {
|
|
if ( !notificationMenuOpen ) {
|
|
const not = [...ShownNotifications.get()];
|
|
not.splice( not.indexOf( id ), 1 );
|
|
ShownNotifications.set( not );
|
|
}
|
|
}
|
|
|
|
// ───────────────────────────────────────────────────────────────────
|
|
|
|
|
|
/**
|
|
* Open the notification menu. Called by toggleNotificationMenu too
|
|
*/
|
|
const openNotificationMenu = () => {
|
|
// Simply show all notifications
|
|
notificationMenuOpen = true;
|
|
const not = [];
|
|
for (let index = 0; index < Notifications.length; index++) {
|
|
not.push( index );
|
|
}
|
|
ShownNotifications.set( not.reverse() );
|
|
}
|
|
|
|
// ───────────────────────────────────────────────────────────────────
|
|
|
|
|
|
/**
|
|
* Close the notification menu. Called by toggleNotificationMenu too
|
|
*/
|
|
const closeNotificationMenu = () => {
|
|
// Hide all notifications
|
|
notificationMenuOpen = true;
|
|
ShownNotifications.set( [] );
|
|
}
|
|
|
|
// ───────────────────────────────────────────────────────────────────
|
|
|
|
|
|
/**
|
|
* Toggle the notification menu (i.e. show all notifications)
|
|
*/
|
|
const toggleNotificationMenu = (): string => {
|
|
if ( notificationMenuOpen ) {
|
|
closeNotificationMenu();
|
|
return 'Toggle notification menu closed';
|
|
} else {
|
|
openNotificationMenu();
|
|
return 'Toggled notification menu open';
|
|
}
|
|
}
|
|
|
|
// ───────────────────────────────────────────────────────────────────
|
|
|
|
|
|
/**
|
|
* Delete all notifications
|
|
*/
|
|
const clearAllNotifications = () => {
|
|
Notifications = [];
|
|
ShownNotifications.set( [] );
|
|
// TODO: Hiding for each individual deleteNotification
|
|
notificationMenuOpen = false;
|
|
}
|
|
|
|
// ───────────────────────────────────────────────────────────────────
|
|
|
|
|
|
/**
|
|
* Delete the newest notifications
|
|
*/
|
|
const clearNewestNotifications = () => {
|
|
const not = [...ShownNotifications.get()];
|
|
not.splice( 0, 1 );
|
|
ShownNotifications.set( not );
|
|
|
|
Notifications.splice( Notifications.length - 1, 1 );
|
|
}
|
|
|
|
|
|
|
|
// ───────────────────────────────────────────────────────────────────
|
|
// ╭───────────────────────────────────────────────╮
|
|
// │ User Interface │
|
|
// ╰───────────────────────────────────────────────╯
|
|
// ───────────────────────────────────────────────────────────────────
|
|
const startNotificationHandler = (gdkmonitor: Gdk.Monitor) => {
|
|
const { TOP, RIGHT } = Astal.WindowAnchor
|
|
|
|
hookToNotificationDaemon();
|
|
|
|
return <window
|
|
cssClasses={["NotificationHandler"]}
|
|
gdkmonitor={gdkmonitor}
|
|
exclusivity={Astal.Exclusivity.EXCLUSIVE}
|
|
anchor={TOP | RIGHT}
|
|
visible={ShownNotifications( list => list.length > 0 )}
|
|
application={App}>
|
|
<box vertical>
|
|
{ShownNotifications( list => list.map( i => {
|
|
// 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();
|
|
return 'Showing all open notifications';
|
|
} else if ( args[ 1 ] == 'hide' ) {
|
|
closeNotificationMenu();
|
|
return 'Hid all notifications';
|
|
} else if ( args[ 1 ] == 'clear' ) {
|
|
clearAllNotifications();
|
|
return 'Cleared all notifications';
|
|
} else if ( args[ 1 ] == 'clear-newest' ) {
|
|
clearNewestNotifications();
|
|
return 'Cleared newest notification';
|
|
} else if ( args[ 1 ] == 'toggle' ) {
|
|
return toggleNotificationMenu();
|
|
} else if ( args[ 1 ] == 'list' ){
|
|
if ( Notifications.length > 0 ) {
|
|
let list = 'Currently unviewed notifications: ';
|
|
for (let index = 0; index < Notifications.length; index++) {
|
|
const element = Notifications[index];
|
|
|
|
list += `\n - (${element.notifdID}) ${element.notification.get_app_name()}: ${element.notification.get_summary()}`;
|
|
}
|
|
return list;
|
|
} else {
|
|
return 'No currently unviewed notifications'
|
|
}
|
|
} else {
|
|
return 'Unknown command!';
|
|
}
|
|
}
|
|
|
|
|
|
export default {
|
|
startNotificationHandler,
|
|
cliHandler
|
|
}
|