[AGS] Bar progress

This commit is contained in:
Janis Hutz 2025-04-19 18:20:32 +02:00
parent 78e472beb8
commit b2f1d8fd9e
8 changed files with 249 additions and 107 deletions

View File

@ -2,38 +2,24 @@ import { App } from "astal/gtk3"
import style from "./style.scss" import style from "./style.scss"
import notifications from "./components/notifications/handler"; import notifications from "./components/notifications/handler";
import Bar from "./components/bar/ui/Bar";
App.start({ App.start({
instanceName: "runner", instanceName: "runner",
css: style, css: style,
main() { main() {
notifications.startNotificationHandler( 0, App.get_monitors()[0] ) notifications.startNotificationHandler( 0, App.get_monitors()[0] );
// TODO: Monitor handling
Bar.Bar( App.get_monitors()[0] );
}, },
requestHandler(request, res) { requestHandler(request, res) {
const args = request.trimStart().split( ' ' ); const args = request.trimStart().split( ' ' );
// Notifications // Notifications (TODO: Handle the arguments in the components themselves)
if ( args[ 0 ] === 'notifier' ) { if ( args[ 0 ] === 'notifier' ) {
if ( args[ 1 ] == 'show' ) { res( notifications.cliHandler( args ) );
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' ) { } else if ( args[ 0 ] === 'bar' ) {
res( Bar.cliHandler( args ) );
} }
}, },
}) })

View File

@ -1,23 +1,36 @@
import { createQuickActionsMenu } from "./QuickActions";
import { GLib } from "astal";
import { Astal, Gdk, Gtk } from "astal/gtk3"; import { Astal, Gdk, Gtk } from "astal/gtk3";
import Hyprland from "./modules/Hyprland";
import Calendar from "./modules/Calendar";
import QuickView from "./modules/QuickView";
const Bar = (gdkmonitor: Gdk.Monitor) => { const Bar = (gdkmonitor: Gdk.Monitor) => {
const { TOP, LEFT, RIGHT } = Astal.WindowAnchor;
return ( return (
<window gdkmonitor={gdkmonitor} <window gdkmonitor={gdkmonitor}
cssClasses={["Bar"]}> cssClasses={["Bar"]}
exclusivity={Astal.Exclusivity.EXCLUSIVE}
anchor={TOP | LEFT | RIGHT}>
<box orientation={Gtk.Orientation.HORIZONTAL} spacing={10}> <box orientation={Gtk.Orientation.HORIZONTAL} spacing={10}>
<box> <box hexpand halign={Gtk.Align.START}>
<Calendar.Time />
<Hyprland.Workspace />
</box> </box>
<label>{windowTitle}</label> <Hyprland.ActiveWindow />
<box> <box hexpand halign={Gtk.Align.END}>
<tray /> <Hyprland.SysTray />
<button icon="quickaction" menu={quickActionMenu} /> <QuickView.QuickView />
</box> </box>
</box> </box>
</window> </window>
); );
} }
export default Bar; const cliHandler = ( args: string[] ): string => {
return 'Not implemented';
}
export default {
Bar,
cliHandler
};

View File

@ -0,0 +1,17 @@
import { GLib, Variable } from "astal"
const Time = ({ format = "%a, %e.%m %H:%M:%S" }) => {
const time = Variable<string>("").poll(1000, () =>
GLib.DateTime.new_now_local().format(format)!)
return <label
className="Time"
onDestroy={() => time.drop()}
label={time()}
/>
}
export default {
Time
}

View File

@ -19,7 +19,7 @@ const SysTray = () => {
} }
const HyprlandWorkspace = () => { const Workspace = () => {
const hypr = AstalHyprland.get_default() const hypr = AstalHyprland.get_default()
return <box className={"HyprlandWorkspaces"}> return <box className={"HyprlandWorkspaces"}>
@ -39,7 +39,7 @@ const HyprlandWorkspace = () => {
} }
const HyprlandActiveWindow = () => { const ActiveWindow = () => {
const hypr = AstalHyprland.get_default(); const hypr = AstalHyprland.get_default();
const focused = bind( hypr, "focusedClient" ); const focused = bind( hypr, "focusedClient" );
@ -51,7 +51,7 @@ const HyprlandActiveWindow = () => {
} }
export default { export default {
HyprlandWorkspace, Workspace,
HyprlandActiveWindow, ActiveWindow,
SysTray SysTray
} }

View File

@ -0,0 +1,106 @@
import { bind } from "astal";
import AstalBattery from "gi://AstalBattery?version=0.1";
import AstalBluetooth from "gi://AstalBluetooth?version=0.1";
import AstalNetwork from "gi://AstalNetwork?version=0.1"
import AstalWp from "gi://AstalWp?version=0.1";
import Brightness from "../../util/brightness";
import { Gtk } from "astal/gtk3";
const QuickView = () => {
return <box>
<Audio></Audio>
<label label="QuickView"></label>
</box>
}
const NetworkWidget = () => {
const network = AstalNetwork.get_default();
const status = bind( network, "state" );
const wifiStrength = bind( network.wifi, 'strength' );
const states = {
"off": ""
}
return <label label={""}></label>
}
const BluetoothWidget = () => {
const bluetooth = AstalBluetooth.get_default();
const enabled = bind( bluetooth, "isPowered" );
const connected = bind( bluetooth, "isConnected" );
}
const BatteryWidget = () => {
const battery = AstalBattery.get_default();
if ( battery.get_is_present() ) {
const states = {
"100": "",
"90": "",
"80": "",
"70": "",
"60": "",
"50": "",
"40": "",
"30": "",
"20": "",
"10": "",
"critical": "",
"charging":"",
"plugged": " ",
}
}
// Else, no battery available -> Don't show the widget
}
const BrightnessWidget = () => {
// TODO: Finish (detect if there is a controllable screen)
const brightness = Brightness.get_default();
const screen_brightness = bind( brightness, "screen" );
return <label label={"🌣" + screen_brightness}></label>
}
const Audio = () => {
const wireplumber = AstalWp.get_default();
if ( wireplumber ) {
// With the states, split up the icons according to number of elements available
const speakerMuted = " ";
const speakersStates = [
"",
"",
""
]
const micStates = {
"on": " ",
"muted": " ",
}
const volume_speakers = bind( wireplumber.defaultSpeaker, 'volume' );
const muted_speakers = bind( wireplumber.defaultSpeaker, 'mute' );
const muted_mic = bind( wireplumber.defaultMicrophone, 'mute' );
return <box orientation={Gtk.Orientation.HORIZONTAL}>
<label label={micStates[ muted_mic ? 'muted' : 'on' ]}></label>
<label label={(muted_speakers ? speakerMuted : volume_speakers.as( v => {
if ( v === 0 ) return speakerMuted;
else if ( v <= 30 ) return speakersStates[ 0 ];
else if ( v <= 70 ) return speakersStates[ 1 ];
else return speakersStates[ 2 ];
} ) )}></label>
<label label={volume_speakers.as( v => { return "" + v } ) }></label>
<label label={wireplumber.default_speaker.get_name()}></label>
</box>
} else {
print( '[ WirePlumber ] Could not connect, Audio support in bar will be missing' );
}
return null;
}
export default {
QuickView
}

View File

@ -0,0 +1,71 @@
import GObject, { register, property } from "astal/gobject"
import { monitorFile, readFileAsync } from "astal/file"
import { exec, execAsync } from "astal/process"
const get = (args: string) => Number(exec(`brightnessctl ${args}`))
const screen = exec(`bash -c "ls -w1 /sys/class/backlight | head -1"`)
const kbd = exec(`bash -c "ls -w1 /sys/class/leds | head -1"`)
@register({ GTypeName: "Brightness" })
export default class Brightness extends GObject.Object {
static instance: Brightness
static get_default() {
if (!this.instance)
this.instance = new Brightness()
return this.instance
}
#kbdMax = get(`--device ${kbd} max`)
#kbd = get(`--device ${kbd} get`)
#screenMax = get("max")
#screen = get("get") / (get("max") || 1)
@property(Number)
get kbd() { return this.#kbd }
set kbd(value) {
if (value < 0 || value > this.#kbdMax)
return
execAsync(`brightnessctl -d ${kbd} s ${value} -q`).then(() => {
this.#kbd = value
this.notify("kbd")
})
}
@property(Number)
get screen() { return this.#screen }
set screen(percent) {
if (percent < 0)
percent = 0
if (percent > 1)
percent = 1
execAsync(`brightnessctl set ${Math.floor(percent * 100)}% -q`).then(() => {
this.#screen = percent
this.notify("screen")
})
}
constructor() {
super()
const screenPath = `/sys/class/backlight/${screen}/brightness`
const kbdPath = `/sys/class/leds/${kbd}/brightness`
monitorFile(screenPath, async f => {
const v = await readFileAsync(f)
this.#screen = Number(v) / this.#screenMax
this.notify("screen")
})
monitorFile(kbdPath, async f => {
const v = await readFileAsync(f)
this.#kbd = Number(v) / this.#kbdMax
this.notify("kbd")
})
}
}

View File

@ -1,68 +0,0 @@
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
}

View File

@ -195,12 +195,29 @@ const startNotificationHandler = (id: number, gdkmonitor: Gdk.Monitor) => {
</window> </window>
} }
const cliHandler = ( args: string[] ): string => {
if ( args[ 1 ] == 'show' ) {
openNotificationMenu( 0 );
return 'Showing all open notifications';
} else if ( args[ 1 ] == 'hide' ) {
closeNotificationMenu( 0 );
return 'Hid all notifications';
} else if ( args[ 1 ] == 'clear' ) {
clearAllNotifications( 0 );
return 'Cleared all notifications';
} else if ( args[ 1 ] == 'clear-newest' ) {
clearNewestNotifications( 0 );
return 'Cleared newest notification';
} else if ( args[ 1 ] == 'toggle' ) {
toggleNotificationMenu( 0 );
return 'Toggled notifications';
} else {
return 'Unknown command!';
}
}
export default { export default {
startNotificationHandler, startNotificationHandler,
openNotificationMenu, cliHandler
closeNotificationMenu,
clearAllNotifications,
clearNewestNotifications,
toggleNotificationMenu
} }