[AGS] Redo basic setup
This commit is contained in:
parent
a9c7b7d7ee
commit
78e472beb8
2
config/astal/.gitignore
vendored
Normal file
2
config/astal/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
node_modules/
|
||||||
|
@girs/
|
39
config/astal/app.ts
Normal file
39
config/astal/app.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { App } from "astal/gtk3"
|
||||||
|
import style from "./style.scss"
|
||||||
|
|
||||||
|
import notifications from "./components/notifications/handler";
|
||||||
|
|
||||||
|
App.start({
|
||||||
|
instanceName: "runner",
|
||||||
|
css: style,
|
||||||
|
main() {
|
||||||
|
notifications.startNotificationHandler( 0, App.get_monitors()[0] )
|
||||||
|
},
|
||||||
|
requestHandler(request, res) {
|
||||||
|
const args = request.trimStart().split( ' ' );
|
||||||
|
|
||||||
|
// Notifications
|
||||||
|
if ( args[ 0 ] === 'notifier' ) {
|
||||||
|
if ( args[ 1 ] == 'show' ) {
|
||||||
|
notifications.openNotificationMenu( 0 );
|
||||||
|
res( 'Showing all open notifications' );
|
||||||
|
} else if ( args[ 1 ] == 'hide' ) {
|
||||||
|
notifications.closeNotificationMenu( 0 );
|
||||||
|
res( 'Hid all notifications' );
|
||||||
|
} else if ( args[ 1 ] == 'clear' ) {
|
||||||
|
notifications.clearAllNotifications( 0 );
|
||||||
|
res( 'Cleared all notifications' );
|
||||||
|
} else if ( args[ 1 ] == 'clear-newest' ) {
|
||||||
|
notifications.clearNewestNotifications( 0 );
|
||||||
|
res( 'Cleared newest notification' );
|
||||||
|
} else if ( args[ 1 ] == 'toggle' ) {
|
||||||
|
notifications.toggleNotificationMenu( 0 );
|
||||||
|
res( 'Toggled notifications' );
|
||||||
|
} else {
|
||||||
|
res( 'Unknown command!' );
|
||||||
|
}
|
||||||
|
} else if ( args[ 0 ] === 'bar' ) {
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
23
config/astal/components/bar/ui/Bar.tsx
Normal file
23
config/astal/components/bar/ui/Bar.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { createQuickActionsMenu } from "./QuickActions";
|
||||||
|
import { GLib } from "astal";
|
||||||
|
import { Astal, Gdk, Gtk } from "astal/gtk3";
|
||||||
|
|
||||||
|
const Bar = (gdkmonitor: Gdk.Monitor) => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<window gdkmonitor={gdkmonitor}
|
||||||
|
cssClasses={["Bar"]}>
|
||||||
|
<box orientation={Gtk.Orientation.HORIZONTAL} spacing={10}>
|
||||||
|
<box>
|
||||||
|
</box>
|
||||||
|
<label>{windowTitle}</label>
|
||||||
|
<box>
|
||||||
|
<tray />
|
||||||
|
<button icon="quickaction" menu={quickActionMenu} />
|
||||||
|
</box>
|
||||||
|
</box>
|
||||||
|
</window>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Bar;
|
76
config/astal/components/bar/ui/QuickActions.tsx
Normal file
76
config/astal/components/bar/ui/QuickActions.tsx
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import Gtk from "gi://Gtk";
|
||||||
|
import AstalNetwork from "gi://AstalNetwork";
|
||||||
|
import AstalBluetooth from "gi://AstalBluetooth";
|
||||||
|
import { exec } from "gi://GLib";
|
||||||
|
|
||||||
|
let quickActionsMenu;
|
||||||
|
|
||||||
|
// Toggle WiFi connection
|
||||||
|
function toggleWiFi() {
|
||||||
|
const network = AstalNetwork.get_default();
|
||||||
|
const wifi = network.get_wifi()!;
|
||||||
|
const state = wifi.get_state();
|
||||||
|
|
||||||
|
if (state === AstalNetwork.WifiState.DISCONNECTED) {
|
||||||
|
wifi.connect();
|
||||||
|
} else {
|
||||||
|
wifi.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle Bluetooth power state
|
||||||
|
function toggleBluetooth() {
|
||||||
|
const bluetooth = AstalBluetooth.get_default();
|
||||||
|
const adapter = bluetooth.get_adapter();
|
||||||
|
const powered = adapter?.get_powered();
|
||||||
|
|
||||||
|
adapter?.set_powered(!powered);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust volume or microphone (opens pavucontrol)
|
||||||
|
function adjustVolume() {
|
||||||
|
exec("pavucontrol");
|
||||||
|
}
|
||||||
|
|
||||||
|
function adjustMic() {
|
||||||
|
exec("pavucontrol");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Power menu
|
||||||
|
function showPowerMenu() {
|
||||||
|
exec("power-menu");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show WiFi network picker
|
||||||
|
function pickWiFi() {
|
||||||
|
const wifi = AstalNetwork.get_default().get_wifi();
|
||||||
|
wifi.show_picker();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show Bluetooth device picker
|
||||||
|
function pickBluetooth() {
|
||||||
|
const bluetooth = AstalBluetooth.get_default();
|
||||||
|
bluetooth.show_picker();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create menu using JSX
|
||||||
|
function createQuickActionsMenu() {
|
||||||
|
quickActionsMenu = (
|
||||||
|
<menu>
|
||||||
|
<item label="📶 WiFi" onActivate={toggleWiFi} />
|
||||||
|
<item label="→ Pick Network" onActivate={pickWiFi} />
|
||||||
|
|
||||||
|
<item label="🔵 Bluetooth" onActivate={toggleBluetooth} />
|
||||||
|
<item label="→ Pick Device" onActivate={pickBluetooth} />
|
||||||
|
|
||||||
|
<item label="🔊 Volume" onActivate={adjustVolume} />
|
||||||
|
<item label="🎙️ Microphone" onActivate={adjustMic} />
|
||||||
|
<item label="🔌 Power" onActivate={showPowerMenu} />
|
||||||
|
</menu>
|
||||||
|
);
|
||||||
|
|
||||||
|
return quickActionsMenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export the function
|
||||||
|
export { createQuickActionsMenu };
|
0
config/astal/components/bar/ui/modules/Calendar.tsx
Normal file
0
config/astal/components/bar/ui/modules/Calendar.tsx
Normal file
57
config/astal/components/bar/ui/modules/Hyprland.tsx
Normal file
57
config/astal/components/bar/ui/modules/Hyprland.tsx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import AstalTray from "gi://AstalTray";
|
||||||
|
import { bind } from "astal";
|
||||||
|
import AstalHyprland from "gi://AstalHyprland";
|
||||||
|
|
||||||
|
const SysTray = () => {
|
||||||
|
const tray = AstalTray.get_default();
|
||||||
|
|
||||||
|
return <box className="SysTray">
|
||||||
|
{bind(tray, "items").as( items => items.map( item => (
|
||||||
|
<button
|
||||||
|
tooltipMarkup={bind(item, "tooltipMarkup")}
|
||||||
|
usePopover={false}
|
||||||
|
actionGroup={bind(item, "actionGroup").as(ag => ["dbusmenu", ag])}
|
||||||
|
menuModel={bind(item, "menuModel")}>
|
||||||
|
<icon gicon={bind(item, "gicon")} />
|
||||||
|
</button>
|
||||||
|
) ) ) }
|
||||||
|
</box>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const HyprlandWorkspace = () => {
|
||||||
|
const hypr = AstalHyprland.get_default()
|
||||||
|
|
||||||
|
return <box className={"HyprlandWorkspaces"}>
|
||||||
|
{bind(hypr, "workspaces").as(wss => wss
|
||||||
|
.filter(ws => !(ws.id >= -99 && ws.id <= -2)) // filter out special workspaces
|
||||||
|
.sort((a, b) => a.id - b.id)
|
||||||
|
.map(ws => (
|
||||||
|
<button
|
||||||
|
className={bind(hypr, "focusedWorkspace").as(fw =>
|
||||||
|
ws === fw ? "HyprlandFocusedWorkspace" : "")}
|
||||||
|
onClicked={() => ws.focus()}>
|
||||||
|
{ws.id}
|
||||||
|
</button>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</box>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const HyprlandActiveWindow = () => {
|
||||||
|
const hypr = AstalHyprland.get_default();
|
||||||
|
const focused = bind( hypr, "focusedClient" );
|
||||||
|
|
||||||
|
return <box className={"HyprlandFocusedClients"} visible={focused.as(Boolean)}>
|
||||||
|
{focused.as( client => (
|
||||||
|
client && <label label={bind( client, "title" ).as( String )} />
|
||||||
|
))}
|
||||||
|
</box>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
HyprlandWorkspace,
|
||||||
|
HyprlandActiveWindow,
|
||||||
|
SysTray
|
||||||
|
}
|
37
config/astal/components/bar/ui/quickactions.scss
Normal file
37
config/astal/components/bar/ui/quickactions.scss
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
@import "colors";
|
||||||
|
|
||||||
|
.toggle-row {
|
||||||
|
background-color: $bg;
|
||||||
|
border-radius: 12px;
|
||||||
|
margin: 6px 0;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid $border;
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 10px 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: background 0.2s ease;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $hover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle {
|
||||||
|
flex: 1;
|
||||||
|
background-color: transparent;
|
||||||
|
text-align: left;
|
||||||
|
&.active {
|
||||||
|
background-color: $accent;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow {
|
||||||
|
width: 40px;
|
||||||
|
background-color: transparent;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
68
config/astal/components/bar/util/hyprland.ts
Normal file
68
config/astal/components/bar/util/hyprland.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import AstalHyprland from "gi://AstalHyprland";
|
||||||
|
|
||||||
|
const getAvailableWorkspaces = (): number[] => {
|
||||||
|
const workspaces: number[] = [];
|
||||||
|
AstalHyprland.get_default().get_workspaces().forEach( val => {
|
||||||
|
workspaces.push( val.get_id() );
|
||||||
|
} )
|
||||||
|
return workspaces;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCurrentWorkspaceID = (): number => {
|
||||||
|
return AstalHyprland.get_default().get_focused_workspace().get_id();
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCurrentWindowTitle = (): string => {
|
||||||
|
return AstalHyprland.get_default().get_focused_client().get_title();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the workspace ID of a window by its address
|
||||||
|
* @param address - The address of the window
|
||||||
|
* @returns The workspace ID
|
||||||
|
*/
|
||||||
|
const getWorkspaceIDOfWindowByAddress = ( address: string ): number => {
|
||||||
|
AstalHyprland.get_default().get_clients().forEach( client => {
|
||||||
|
if ( client.get_address() === address ) {
|
||||||
|
return client.get_workspace().get_id();
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface HyprlandSubscriptions {
|
||||||
|
[key: string]: ( data: string ) => void;
|
||||||
|
}
|
||||||
|
const hooks: HyprlandSubscriptions = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an event listener for Hyprland events.
|
||||||
|
* @param event - A hyprland IPC event. See https://wiki.hyprland.org/IPC/. Useful events include: urgent, windowtitlev2, workspace, createworkspacev2, destroyworkspacev2, activewindowv2
|
||||||
|
* @param cb - The callback function
|
||||||
|
*/
|
||||||
|
const subscribeToUpdates = ( event: string, cb: ( data: string ) => void ): void => {
|
||||||
|
hooks[ event ] = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen to events. Must be called at some point if events are to be listened for
|
||||||
|
*/
|
||||||
|
const listen = () => {
|
||||||
|
AstalHyprland.get_default().connect( "event", ( name: string, data: string ) => {
|
||||||
|
if ( hooks[ name ] ) {
|
||||||
|
hooks[ name ]( data );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getAvailableWorkspaces,
|
||||||
|
getCurrentWorkspaceID,
|
||||||
|
getCurrentWindowTitle,
|
||||||
|
getWorkspaceIDOfWindowByAddress,
|
||||||
|
subscribeToUpdates,
|
||||||
|
listen
|
||||||
|
}
|
0
config/astal/components/bar/util/sysinfo.ts
Normal file
0
config/astal/components/bar/util/sysinfo.ts
Normal file
206
config/astal/components/notifications/handler.tsx
Normal file
206
config/astal/components/notifications/handler.tsx
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
/*
|
||||||
|
* dotfiles - handler.ts
|
||||||
|
*
|
||||||
|
* Created by Janis Hutz 03/21/2025, Licensed under the GPL V3 License
|
||||||
|
* https://janishutz.com, development@janishutz.com
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Astal, Gtk, Gdk } from "astal/gtk3"
|
||||||
|
import Notifd from "gi://AstalNotifd";
|
||||||
|
import Notification from "./notifications";
|
||||||
|
import { type Subscribable } from "astal/binding";
|
||||||
|
import { Variable, bind, timeout } from "astal"
|
||||||
|
|
||||||
|
// Config
|
||||||
|
const TIMEOUT_DELAY = 5000;
|
||||||
|
|
||||||
|
class Notifier implements Subscribable {
|
||||||
|
private display: Map<number, Gtk.Widget> = new Map();
|
||||||
|
private notifications: Map<number, Notifd.Notification> = new Map();
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
this.notifd.connect( 'notified', ( _, id ) => {
|
||||||
|
this.add( id );
|
||||||
|
} );
|
||||||
|
|
||||||
|
this.notifd.connect( 'resolved', ( _, id ) => {
|
||||||
|
this.delete( id );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
openNotificationMenu () {
|
||||||
|
// Show all notifications that have not been cleared
|
||||||
|
if ( this.notifications.size > 0 ) {
|
||||||
|
this.menuOpen = true;
|
||||||
|
this.notifications.forEach( ( _, id ) => {
|
||||||
|
this.show( id );
|
||||||
|
} )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hideNotifications () {
|
||||||
|
this.menuOpen = false;
|
||||||
|
this.notifications.forEach( ( _, id ) => {
|
||||||
|
this.hide( 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const notifiers: Map<number, Notifier> = new Map();
|
||||||
|
const deleteHelper = ( id: number, instanceID: number ) => {
|
||||||
|
notifiers.get( instanceID )?.delete( id );
|
||||||
|
}
|
||||||
|
|
||||||
|
const openNotificationMenu = ( id: number ) => {
|
||||||
|
notifiers.get( id )?.openNotificationMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeNotificationMenu = ( id: number ) => {
|
||||||
|
notifiers.get( id )?.hideNotifications();
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleNotificationMenu = ( id: number ) => {
|
||||||
|
notifiers.get( id )?.toggleNotificationMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearAllNotifications = ( id: number ) => {
|
||||||
|
notifiers.get( id )?.clearAllNotifications();
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearNewestNotifications = ( id: number ) => {
|
||||||
|
notifiers.get( id )?.clearNewestNotification();
|
||||||
|
}
|
||||||
|
|
||||||
|
const startNotificationHandler = (id: number, gdkmonitor: Gdk.Monitor) => {
|
||||||
|
const { TOP, RIGHT } = Astal.WindowAnchor
|
||||||
|
const notifier: Notifier = new Notifier( id );
|
||||||
|
notifiers.set( id, notifier );
|
||||||
|
|
||||||
|
return <window
|
||||||
|
className="NotificationHandler"
|
||||||
|
gdkmonitor={gdkmonitor}
|
||||||
|
exclusivity={Astal.Exclusivity.EXCLUSIVE}
|
||||||
|
anchor={TOP | RIGHT}>
|
||||||
|
<box vertical noImplicitDestroy>
|
||||||
|
{bind(notifier)}
|
||||||
|
</box>
|
||||||
|
</window>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
startNotificationHandler,
|
||||||
|
openNotificationMenu,
|
||||||
|
closeNotificationMenu,
|
||||||
|
clearAllNotifications,
|
||||||
|
clearNewestNotifications,
|
||||||
|
toggleNotificationMenu
|
||||||
|
}
|
125
config/astal/components/notifications/notifications.scss
Normal file
125
config/astal/components/notifications/notifications.scss
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
@use "sass:string";
|
||||||
|
|
||||||
|
@function gtkalpha($c, $a) {
|
||||||
|
@return string.unquote("alpha(#{$c},#{$a})");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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"};
|
||||||
|
$error: red;
|
||||||
|
|
||||||
|
window.NotificationPopups {
|
||||||
|
all: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
eventbox.Notification {
|
||||||
|
|
||||||
|
&:first-child>box {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child>box {
|
||||||
|
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);
|
||||||
|
|
||||||
|
.header {
|
||||||
|
|
||||||
|
.app-name {
|
||||||
|
color: gtkalpha($error, .8);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-icon {
|
||||||
|
color: gtkalpha($error, .6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
padding: .5rem;
|
||||||
|
color: gtkalpha($fg-color, 0.5);
|
||||||
|
|
||||||
|
.app-icon {
|
||||||
|
margin: 0 .4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-name {
|
||||||
|
margin-right: .3rem;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-left: .4rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.time {
|
||||||
|
margin: 0 .4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: .2rem;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
separator {
|
||||||
|
margin: 0 .4rem;
|
||||||
|
background-color: gtkalpha($fg-color, .1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
margin: 1rem;
|
||||||
|
margin-top: .5rem;
|
||||||
|
|
||||||
|
.summary {
|
||||||
|
font-size: 1.2em;
|
||||||
|
color: $fg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
color: gtkalpha($fg-color, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.image {
|
||||||
|
border: 1px solid gtkalpha($fg-color, .02);
|
||||||
|
margin-right: .5rem;
|
||||||
|
border-radius: 9px;
|
||||||
|
min-width: 100px;
|
||||||
|
min-height: 100px;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
margin: 1rem;
|
||||||
|
margin-top: 0;
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin: 0 .3rem;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
112
config/astal/components/notifications/notifications.tsx
Normal file
112
config/astal/components/notifications/notifications.tsx
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
// From astal examples
|
||||||
|
|
||||||
|
import { GLib } from "astal"
|
||||||
|
import { Gtk, Astal } from "astal/gtk3"
|
||||||
|
import { type EventBox } from "astal/gtk3/widget"
|
||||||
|
import Notifd from "gi://AstalNotifd"
|
||||||
|
|
||||||
|
const isIcon = (icon: string) =>
|
||||||
|
!!Astal.Icon.lookup_icon(icon)
|
||||||
|
|
||||||
|
const fileExists = (path: string) =>
|
||||||
|
GLib.file_test(path, GLib.FileTest.EXISTS)
|
||||||
|
|
||||||
|
const time = (time: number, format = "%H:%M") => GLib.DateTime
|
||||||
|
.new_from_unix_local(time)
|
||||||
|
.format(format)!
|
||||||
|
|
||||||
|
const urgency = (n: Notifd.Notification) => {
|
||||||
|
const { LOW, NORMAL, CRITICAL } = Notifd.Urgency
|
||||||
|
// match operator when?
|
||||||
|
switch (n.urgency) {
|
||||||
|
case LOW: return "low"
|
||||||
|
case CRITICAL: return "critical"
|
||||||
|
case NORMAL:
|
||||||
|
default: return "normal"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
delete( id: number, instanceID: number ): void
|
||||||
|
setup(self: EventBox): void
|
||||||
|
onHoverLost(self: EventBox): 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 { START, CENTER, END } = Gtk.Align
|
||||||
|
|
||||||
|
return <eventbox
|
||||||
|
className={`Notification ${urgency(n)}`}
|
||||||
|
setup={setup}
|
||||||
|
onHoverLost={onHoverLost}>
|
||||||
|
<box vertical>
|
||||||
|
<box className="header">
|
||||||
|
{(n.appIcon || n.desktopEntry) && <icon
|
||||||
|
className="app-icon"
|
||||||
|
visible={Boolean(n.appIcon || n.desktopEntry)}
|
||||||
|
icon={n.appIcon || n.desktopEntry}
|
||||||
|
/>}
|
||||||
|
<label
|
||||||
|
className="app-name"
|
||||||
|
halign={START}
|
||||||
|
truncate
|
||||||
|
label={n.appName || "Unknown"}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
className="time"
|
||||||
|
hexpand
|
||||||
|
halign={END}
|
||||||
|
label={time(n.time)}
|
||||||
|
/>
|
||||||
|
<button onClicked={() => del( id, instance )}>
|
||||||
|
<icon icon="window-close-symbolic" />
|
||||||
|
</button>
|
||||||
|
</box>
|
||||||
|
<Gtk.Separator visible />
|
||||||
|
<box className="content">
|
||||||
|
{n.image && fileExists(n.image) && <box
|
||||||
|
valign={START}
|
||||||
|
className="image"
|
||||||
|
css={`background-image: url('${n.image}')`}
|
||||||
|
/>}
|
||||||
|
{n.image && isIcon(n.image) && <box
|
||||||
|
expand={false}
|
||||||
|
valign={START}
|
||||||
|
className="icon-image">
|
||||||
|
<icon icon={n.image} expand halign={CENTER} valign={CENTER} />
|
||||||
|
</box>}
|
||||||
|
<box vertical>
|
||||||
|
<label
|
||||||
|
className="summary"
|
||||||
|
halign={START}
|
||||||
|
xalign={0}
|
||||||
|
label={n.summary}
|
||||||
|
truncate
|
||||||
|
/>
|
||||||
|
{n.body && <label
|
||||||
|
className="body"
|
||||||
|
wrap
|
||||||
|
useMarkup
|
||||||
|
halign={START}
|
||||||
|
xalign={0}
|
||||||
|
justifyFill
|
||||||
|
label={n.body}
|
||||||
|
/>}
|
||||||
|
</box>
|
||||||
|
</box>
|
||||||
|
{n.get_actions().length > 0 && <box className="actions">
|
||||||
|
{n.get_actions().map(({ label, id }) => (
|
||||||
|
<button
|
||||||
|
hexpand
|
||||||
|
onClicked={() => n.invoke(id)}>
|
||||||
|
<label label={label} halign={CENTER} hexpand />
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</box>}
|
||||||
|
</box>
|
||||||
|
</eventbox>
|
||||||
|
}
|
21
config/astal/env.d.ts
vendored
Normal file
21
config/astal/env.d.ts
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
declare const SRC: string
|
||||||
|
|
||||||
|
declare module "inline:*" {
|
||||||
|
const content: string
|
||||||
|
export default content
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "*.scss" {
|
||||||
|
const content: string
|
||||||
|
export default content
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "*.blp" {
|
||||||
|
const content: string
|
||||||
|
export default content
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "*.css" {
|
||||||
|
const content: string
|
||||||
|
export default content
|
||||||
|
}
|
6
config/astal/package.json
Normal file
6
config/astal/package.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "astal-shell",
|
||||||
|
"dependencies": {
|
||||||
|
"astal": "/usr/share/astal/gjs"
|
||||||
|
}
|
||||||
|
}
|
20
config/astal/style.scss
Normal file
20
config/astal/style.scss
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// 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"};
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
14
config/astal/tsconfig.json
Normal file
14
config/astal/tsconfig.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/tsconfig",
|
||||||
|
"compilerOptions": {
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"strict": true,
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "ES2022",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
// "checkJs": true,
|
||||||
|
// "allowJs": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"jsxImportSource": "astal/gtk3",
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user