Files
dotfiles/config/astal/components/bar/modules/Hyprland.tsx
2025-10-17 09:47:18 +02:00

195 lines
6.0 KiB
TypeScript

import {
GObject, bind,
exec,
readFile
} from 'astal';
import AstalHyprland from 'gi://AstalHyprland';
import AstalTray from 'gi://AstalTray';
import {
Gtk
} from 'astal/gtk4';
import Pango from 'gi://Pango?version=1.0';
const hypr = AstalHyprland.get_default();
const SYNC = GObject.BindingFlags.SYNC_CREATE;
const SysTray = () => {
const trayBox = new Gtk.Box( {
'cssClasses': [ 'bar-button' ]
} );
const tray = AstalTray.get_default();
const trayItems = new Map<string, Gtk.MenuButton>();
const trayAddedHandler = tray.connect( 'item-added', ( _, id ) => {
const item = tray.get_item( id );
const popover = Gtk.PopoverMenu.new_from_model( item.menu_model );
const icon = new Gtk.Image();
const button = new Gtk.MenuButton( {
popover,
'child': icon,
'cssClasses': [ 'tray-item' ],
} );
item.bind_property(
'gicon', icon, 'gicon', SYNC
);
popover.insert_action_group( 'dbusmenu', item.action_group );
item.connect( 'notify::action-group', () => {
popover.insert_action_group( 'dbusmenu', item.action_group );
} );
trayItems.set( id, button );
trayBox.append( button );
} );
const trayRemovedHandler = tray.connect( 'item-removed', ( _, id ) => {
const button = trayItems.get( id );
if ( button ) {
trayBox.remove( button );
button.run_dispose();
trayItems.delete( id );
}
} );
trayBox.connect( 'destroy', () => {
tray.disconnect( trayAddedHandler );
tray.disconnect( trayRemovedHandler );
} );
return trayBox;
};
const Workspace = () => {
return (
<box>
{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
cssClasses={bind( hypr, 'focusedWorkspace' ).as( fw => ws === fw
? [
'focused-workspace-button',
'workspace-button',
]
: [ 'workspace-button' ], )}
onButtonPressed={() => ws.focus()}
child={<label label={String( ws.id )}></label>}
></button>
) ), )}
</box>
);
};
/**
* Displays the name of the currently active window and provides a popover for
* displaying all available clients
*/
const ActiveWindow = () => {
const focused = bind( hypr, 'focusedClient' );
const WindowPopover = (): Gtk.Popover => {
// Set up boxes + Popover
const popover = new Gtk.Popover();
const popoverBox = WindowPopoverBox();
popover.set_child( popoverBox );
return popover;
};
const windowPopover = WindowPopover();
// ───────────────────────────────────────────────────────────────────
// Return fully assembled HyprlandFocusedClient box
// ───────────────────────────────────────────────────────────────────
return (
<box visible={focused.as( Boolean )}>
<button
onClicked={() => windowPopover.popup()}
cssClasses={[ 'bar-button' ]}
child={
focused.as( client => client && (
<label
maxWidthChars={70}
ellipsize={Pango.EllipsizeMode.END}
label={bind( client, 'title' ).as( String )} />
), )
}></button>
{windowPopover}
</box >
);
};
type submaps = 'device' | 'launch' | 'workspace' | 'windowing' | 'screenshotting' | 'notifications' | '';
const ModeStatus = () => {
let isUsingHyprmode = false;
try {
const path = exec( 'bash -c "cd ~ && pwd"' ) + '/.config/hyprmode';
isUsingHyprmode = readFile( path ).trim() === 'y';
} catch ( e ) {
printerr( 'Failed to read hyprmode config', e );
}
const label = new Gtk.Label();
if ( !isUsingHyprmode ) return label;
print( '==> Using hyprmode config' );
const map = {
'device': 'D',
'launch': 'L',
'workspace': 'W',
'windowing': 'M',
'screenshotting': 'S',
'notifications': 'N',
'': 'N'
};
label.label = 'N';
label.cssClasses = [ 'mode-status' ];
// TODO: Possibly add popover to it that lists binds
hypr.connect( 'submap', ( _, name: submaps ) => {
label.label = map[name];
label.cssClasses = [
'mode-status',
name + '-mode'
];
} );
return label;
};
const WindowPopoverBox = () => {
return <box vertical>
<label label={'Available Windows'} cssClasses={[ 'title-2' ]}></label>
<Gtk.Separator marginTop={5} marginBottom={5}></Gtk.Separator>
<box vertical>
{bind( hypr, 'clients' ).as( clients => {
return clients.map( client => {
return <button child={
<box>
<label label={bind( client, 'workspace' ).as( w => `(WS ${ w.name })` )}></label>
<label label={bind( client, 'initialClass' ).as( c => `[${ c }]` )}></label>
<label label={bind( client, 'title' )}></label>
</box>
}
onClicked={() => client.focus()}
></button>;
} );
} )}
</box>
</box>;
};
export default {
Workspace,
ActiveWindow,
SysTray,
ModeStatus
};