diff --git a/config/astal/app.ts b/config/astal/app.ts index 11f5328..b0f7fed 100644 --- a/config/astal/app.ts +++ b/config/astal/app.ts @@ -1,16 +1,16 @@ -import { App } from "astal/gtk3" +import { App } from "astal/gtk4" import style from "./style.scss" import notifications from "./components/notifications/handler"; -import Bar from "./components/bar/ui/Bar"; +// import Bar from "./components/bar/ui/Bar"; App.start({ instanceName: "runner", css: style, main() { - notifications.startNotificationHandler( 0, App.get_monitors()[0] ); + notifications.startNotificationHandler( App.get_monitors()[0] ); // TODO: Monitor handling - Bar.Bar( App.get_monitors()[0] ); + // Bar.Bar( App.get_monitors()[0] ); }, requestHandler(request, res) { const args = request.trimStart().split( ' ' ); @@ -19,7 +19,7 @@ App.start({ if ( args[ 0 ] === 'notifier' ) { res( notifications.cliHandler( args ) ); } else if ( args[ 0 ] === 'bar' ) { - res( Bar.cliHandler( args ) ); + // res( Bar.cliHandler( args ) ); } }, }) diff --git a/config/astal/components/bar/ui/modules/Hyprland.tsx b/config/astal/components/bar/ui/modules/Hyprland.tsx index 9e42d90..f1c820b 100644 --- a/config/astal/components/bar/ui/modules/Hyprland.tsx +++ b/config/astal/components/bar/ui/modules/Hyprland.tsx @@ -1,6 +1,7 @@ import AstalTray from "gi://AstalTray"; import { bind, Variable } from "astal"; import AstalHyprland from "gi://AstalHyprland"; +import { Gtk } from "astal/gtk4"; const SysTray = () => { const tray = AstalTray.get_default(); @@ -48,12 +49,12 @@ const ActiveWindow = () => { visible.set( !visible.get() ); } - return - + v )} name="popover-container"> diff --git a/config/astal/components/notifications/handler.tsx b/config/astal/components/notifications/handler.tsx index a8b317a..b824f96 100644 --- a/config/astal/components/notifications/handler.tsx +++ b/config/astal/components/notifications/handler.tsx @@ -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 = new Map(); - private notifications: Map = new Map(); +interface NotificationDetails { + notification: Notifd.Notification; + backendID: number; + notifdID: number; +} - private notifd: Notifd.Notifd; - private subscriberData: Variable = 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 = 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 = 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 - {bind(notifier)} + {ShownNotificationsCount( n => range( n ).map( i => { + print( 'Rendering' ); + // i is index in ShownNotifications array + return + } ) ) } } 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!'; diff --git a/config/astal/components/notifications/notifications.scss b/config/astal/components/notifications/notifications.scss index a32f08b..6acbd53 100644 --- a/config/astal/components/notifications/notifications.scss +++ b/config/astal/components/notifications/notifications.scss @@ -9,11 +9,17 @@ $fg-color: #{"@theme_fg_color"}; $bg-color: #{"@theme_bg_color"}; $error: red; -window.NotificationPopups { +window.NotificationHandler { all: unset; } -eventbox.Notification { +box.Notification { + min-width: 400px; + border-radius: 13px; + background-color: $bg-color; + margin: .5rem 1rem .5rem 1rem; + box-shadow: 2px 3px 8px 0 gtkalpha(black, .4); + border: 1pt solid gtkalpha($fg-color, .03); &:first-child>box { margin-top: 1rem; @@ -23,16 +29,6 @@ eventbox.Notification { margin-bottom: 1rem; } - // eventboxes can not take margins so we style its inner box instead - >box { - min-width: 400px; - border-radius: 13px; - background-color: $bg-color; - margin: .5rem 1rem .5rem 1rem; - box-shadow: 2px 3px 8px 0 gtkalpha(black, .4); - border: 1pt solid gtkalpha($fg-color, .03); - } - &.critical>box { border: 1pt solid gtkalpha($error, .4); diff --git a/config/astal/components/notifications/notifications.tsx b/config/astal/components/notifications/notifications.tsx index de4e7b2..a3bd26f 100644 --- a/config/astal/components/notifications/notifications.tsx +++ b/config/astal/components/notifications/notifications.tsx @@ -1,12 +1,9 @@ // From astal examples import { GLib } from "astal" -import { Gtk, Astal } from "astal/gtk3" -import { type EventBox } from "astal/gtk3/widget" +import { Gtk } from "astal/gtk4" import Notifd from "gi://AstalNotifd" - -const isIcon = (icon: string) => - !!Astal.Icon.lookup_icon(icon) +// import Pango from "gi://Pango?version=1.0" const fileExists = (path: string) => GLib.file_test(path, GLib.FileTest.EXISTS) @@ -27,86 +24,80 @@ const urgency = (n: Notifd.Notification) => { } type Props = { - delete( id: number, instanceID: number ): void - setup(self: EventBox): void - onHoverLost(self: EventBox): void + delete( id: number ): void notification: Notifd.Notification id: number - instanceID: number } export default function Notification(props: Props) { - const { notification: n, onHoverLost, setup, id: id, delete: del, instanceID: instance } = props + const { notification: n, id: id, delete: del } = props const { START, CENTER, END } = Gtk.Align - return - - - {(n.appIcon || n.desktopEntry) && } - - - - {n.image && fileExists(n.image) && } - {n.image && isIcon(n.image) && - - } - - - - {n.get_actions().length > 0 && - {n.get_actions().map(({ label, id }) => ( - - ))} - } + return + + {(n.appIcon || n.desktopEntry) ? : } + - + + + {n.image && fileExists(n.image) ? + + + : } + {n.image ? + + + : } + + + {n.body ? + + {n.get_actions().length > 0 ? + {n.get_actions().map(({ label, id }) => ( + + ))} + : } + } diff --git a/config/astal/style.scss b/config/astal/style.scss index 1d0d3a9..b6a5681 100644 --- a/config/astal/style.scss +++ b/config/astal/style.scss @@ -1,20 +1,22 @@ // https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gtk/theme/Adwaita/_colors-public.scss -$fg-color: #{"@theme_fg_color"}; -$bg-color: #{"@theme_bg_color"}; +/* $fg-color: #{"@theme_fg_color"}; */ +/* $bg-color: #{"@theme_bg_color"}; */ +/**/ +/* window.Bar { */ +/* background: transparent; */ +/* color: $fg-color; */ +/* font-weight: bold; */ +/**/ +/* >centerbox { */ +/* background: $bg-color; */ +/* border-radius: 10px; */ +/* margin: 8px; */ +/* } */ +/**/ +/* button { */ +/* border-radius: 8px; */ +/* margin: 2px; */ +/* } */ +/* } */ -window.Bar { - background: transparent; - color: $fg-color; - font-weight: bold; - - >centerbox { - background: $bg-color; - border-radius: 10px; - margin: 8px; - } - - button { - border-radius: 8px; - margin: 2px; - } -} +@use './components/notifications/notifications.scss' diff --git a/config/astal/tsconfig.json b/config/astal/tsconfig.json index 9471e35..a92bc43 100644 --- a/config/astal/tsconfig.json +++ b/config/astal/tsconfig.json @@ -9,6 +9,6 @@ // "checkJs": true, // "allowJs": true, "jsx": "react-jsx", - "jsxImportSource": "astal/gtk3", + "jsxImportSource": "astal/gtk4", } } diff --git a/notes.md b/notes.md index fe05c76..54d5c5c 100644 --- a/notes.md +++ b/notes.md @@ -34,6 +34,8 @@ - [ ] Rofi - [ ] Spotlight-Search (or replace with anyrun) - [ ] Wallpaper selector (that automatically triggers the theming script) +- [ ] Kitty + - [ ] Configure colours - [ ] Hyprland - [ ] Keybinds: Resize window, move window, open calculator, plus more programs - [ ] Read docs @@ -42,9 +44,12 @@ - [ ] New image viewer (eog) - [ ] Other pdf reader (maybe -> zathura) - [ ] Maybe TUI archive manager (felix-rs) -- [ ] Lazygit: Configure -- [x] Nvim (other repo) +- [ ] Lazygit + - [ ] Configure +- [ ] Nvim (other repo) - [x] Replace notification handler (noice) + - [ ] Configure formatters (of Java, Cpp, TS/JS/Vue, Python) + - [ ] Maybe: Add extra configs to commentbox - [ ] Yazi - [ ] More keybinds - [ ] Configure @@ -57,7 +62,8 @@ - [ ] Theming script - [ ] Installer for configs - [ ] Vivado cleanup (run after vivado and hope vivado is blocking (or simply execute vivado in /tmp)) - - [ ] migrate to zoxide from autojump + - [x] migrate to zoxide from autojump + - [ ] properly swap escape and caps (at lowest level possible) Using astal (https://aylur.github.io/astal, which is gjs based), write a component that takes as argument a UIComponent array (pre-defined interface) and depending on what subclass of that interface it is renders a different component for each element.