ags: lot of stuff lmao

This commit is contained in:
retrozinndev
2025-02-28 10:21:37 -03:00
parent ff4365ab6d
commit 1a1a5d63f8
31 changed files with 748 additions and 217 deletions
+23 -1
View File
@@ -1 +1,23 @@
//TODO
//TODO Needs more work
import { Gtk, Widget } from "astal/gtk3";
type CalendarProps = Pick<Widget.BoxProps,
"name"
| "className"
| "css"
| "expand"
| "halign"
| "valign"> & {
showWeekDays: boolean;
showHeader: boolean;
fillGrid: boolean; // I need a better name for this LMAOOO
};
export function Calendar(props?: Partial<CalendarProps>): Gtk.Widget {
return new Widget.Box({
...props,
children: []
} as Widget.BoxProps);
}
+65 -11
View File
@@ -1,18 +1,72 @@
import { Astal, Gtk, Widget } from "astal/gtk3";
import { Binding } from "astal";
import { Astal, Gdk, Gtk, Widget } from "astal/gtk3";
const { TOP, BOTTOM, LEFT, RIGHT }: typeof Astal.WindowAnchor = Astal.WindowAnchor;
/**
* Creates a screen-size window and opens the provided window after it.
* When clicking in the transparent background window, it closes(hides)
* the provided window.
* @param window the window to be rendered and closed when clicking outside of it
*/
export function PopupWindow(window: Gtk.Window) {
const bgWindow: Gtk.Window = new Widget.Window({
namespace: "popup-bg-window",
export interface PopupWindowProps {
className?: string | Binding<string | undefined>;
namespace: string | Binding<string | undefined>;
visible?: boolean | Binding<boolean | undefined>;
halign?: Gtk.Align | Binding<Gtk.Align | undefined>;
valign?: Gtk.Align | Binding<Gtk.Align | undefined>;
hexpand?: boolean | Binding<boolean | undefined>;
vexpand?: boolean | Binding<boolean | undefined>;
expand?: boolean | Binding<boolean | undefined>;
monitor?: number | Binding<number | undefined>;
marginTop?: number | Binding<number | undefined>;
marginBottom?: number | Binding<number | undefined>;
marginLeft?: number | Binding<number | undefined>;
marginRight?: number | Binding<number | undefined>;
widthRequest?: number | Binding<number | undefined>;
heightRequest?: number | Binding<number | undefined>;
layer?: Astal.Layer | Binding<Astal.Layer | undefined>;
onClose?: () => void;
child: Gtk.Widget;
}
export function PopupWindow(props: PopupWindowProps): Widget.Window {
return new Widget.Window({
namespace: props?.namespace || "popup-window",
className: "popup-window",
anchor: TOP | BOTTOM | LEFT | RIGHT,
exclusivity: Astal.Exclusivity.NORMAL,
keymode: Astal.Keymode.EXCLUSIVE,
layer: props?.layer || Astal.Layer.OVERLAY,
focusOnMap: true,
visible: props?.visible,
acceptFocus: true,
monitor: props?.monitor || 0,
onButtonPressEvent: (_, event: Gdk.Event) => {
const [, posX, posY] = event.get_coords();
const childAllocation = _.get_child()!.get_allocation();
} as Widget.WindowProps);
if((posX < childAllocation.x || posX > (childAllocation.x + childAllocation.width)) ||
(posY < childAllocation.y || posY > (childAllocation.y + childAllocation.height))) {
_.hide();
props?.onClose && props.onClose();
}
},
onKeyPressEvent: (_, event: Gdk.Event) =>
event.get_keyval()[1] === Gdk.KEY_Escape && _.hide(),
child: new Widget.Box({
className: (props?.className instanceof Binding) ?
props.className.as((clsName: string|undefined) =>
`popup ${clsName || ""}`)
: `popup ${props?.className || ""}`,
halign: props?.halign || Gtk.Align.CENTER,
valign: props?.valign || Gtk.Align.CENTER,
expand: props?.expand || false,
widthRequest: props?.widthRequest,
heightRequest: props?.heightRequest,
hexpand: props?.hexpand || false,
visible: true,
vexpand: props?.vexpand || false,
marginTop: props?.marginTop || 0,
marginBottom: props?.marginBottom || 0,
marginLeft: props?.marginLeft || 0,
marginRight: props?.marginRight || 0,
child: props.child
} as Widget.BoxProps)
} as Widget.WindowProps);;
}
+2 -2
View File
@@ -20,11 +20,11 @@ export function Separator(props: SeparatorProps) {
}
.separator-horizontal {
padding-bottom: ${props.size || 1 }px;
margin: 7px 4px;
margin: 4px 4px;
}
.separator-vertical {
padding-right: ${props.size || 1 }px;
margin: 4px 7px;
margin: 7px 7px;
}`,
} as Widget.BoxProps);
}
+1 -4
View File
@@ -1,13 +1,10 @@
import { bind, Process } from "astal";
import { Widget } from "astal/gtk3";
import AstalWp from "gi://AstalWp";
import { Wireplumber } from "../../scripts/volume";
import { ControlCenter } from "../../window/ControlCenter";
const wp = AstalWp.get_default();
export function Audio() {
return wp && new Widget.EventBox({
return new Widget.EventBox({
className: bind(ControlCenter, "visible").as((visible: boolean) =>
visible ? "audio open" : "audio"),
onClick: () => Process.exec_async("astal toggle control-center", () => {}),
+6 -4
View File
@@ -12,6 +12,8 @@ export function FocusedClient() {
children: [
new Widget.Icon({
className: "icon",
vexpand: true,
css: ".icon { font-size: 18px; }",
icon: bind(hyprland, "focusedClient").as((client: AstalHyprland.Client) =>
client ?
(getAppIcon(client.initialClass) || client.initialClass)
@@ -29,15 +31,15 @@ export function FocusedClient() {
new Widget.Label({
className: "class",
xalign: 0,
max_width_chars: 65,
truncate: false,
maxWidthChars: 50,
truncate: true,
label: bind(focusedClient, "class")
} as Widget.LabelProps),
new Widget.Label({
className: "title",
xalign: 0,
max_width_chars: 48,
truncate: false,
maxWidthChars: 45,
truncate: true,
label: bind(focusedClient, "title")
} as Widget.LabelProps)
] : []
+9 -8
View File
@@ -2,13 +2,14 @@ import { Widget } from "astal/gtk3";
import AstalHyprland from "gi://AstalHyprland";
export function Logo() {
return new Widget.Box({
return new Widget.EventBox({
onClickRelease: () => AstalHyprland.get_default().dispatch("exec", "anyrun"),
className: "logo",
//tooltipText: tr("bar.logo.tooltip"),
child: new Widget.Button({
onClick: () => AstalHyprland.get_default().dispatch("exec", "anyrun"),
className: "nf",
label: "",
} as Widget.ButtonProps)
} as Widget.BoxProps);
child: new Widget.Box({
child: new Widget.Label({
className: "nf",
label: "",
} as Widget.LabelProps)
} as Widget.BoxProps)
} as Widget.EventBoxProps);
}
+7 -3
View File
@@ -82,12 +82,14 @@ export function Media(): Gtk.Widget {
className: "player-icon nf",
label: bind(players[0], "busName").as((busName: string) => {
const playerName: string = busName.split('.')[busName.split('.').length-1];
return playerIcons[playerName as keyof typeof playerIcons] || "󰎇";
return playerIcons[playerName.toLowerCase() as keyof typeof playerIcons] || "󰎇";
})
} as Widget.LabelProps),
new Widget.Label({
className: "title",
label: bind(players[0], "title").as((title: string) => title || "No Title")
label: bind(players[0], "title").as((title: string) => title || "No Title"),
maxWidthChars: 20,
truncate: true
} as Widget.LabelProps),
Separator({
orientation: Gtk.Orientation.VERTICAL,
@@ -97,7 +99,9 @@ export function Media(): Gtk.Widget {
} as SeparatorProps),
new Widget.Label({
className: "artist",
label: bind(players[0], "artist").as((artist: string) => artist || "No Artist")
label: bind(players[0], "artist").as((artist: string) => artist || "No Artist"),
maxWidthChars: 18,
truncate: true
} as Widget.LabelProps)
] : new Widget.Label({
label: "Crazy to think this widget haven't disappeared yet!"
+52 -7
View File
@@ -1,4 +1,4 @@
import { AstalIO, bind, GLib, Process, timeout } from "astal";
import { AstalIO, bind, Binding, GLib, Process, timeout } from "astal";
import { Gtk, Widget } from "astal/gtk3";
import AstalMpris from "gi://AstalMpris";
@@ -19,10 +19,9 @@ export const BigMedia: Gtk.Widget = new Widget.Box({
className: "image",
hexpand: false,
orientation: Gtk.Orientation.VERTICAL,
visible: bind(players[0], "coverArt").as((coverArt: string) =>
coverArt !== ""),
css: bind(players[0], "coverArt").as((coverArt: string) =>
`.image { background-image: url('${coverArt}'); }`),
visible: getAlbumArt(players[0]).as(Boolean),
css: getAlbumArt(players[0]).as((artUrl: string|undefined) =>
artUrl ? `.image { background-image: url('${artUrl}'); }` : undefined),
width_request: 132,
height_request: 128
} as Widget.BoxProps)
@@ -35,13 +34,15 @@ export const BigMedia: Gtk.Widget = new Widget.Box({
className: "title",
tooltipText: bind(players[0], "title").as((title: string) => !title ? "No Title" : title),
label: bind(players[0], "title").as((title: string) => !title ? "No Title" : title),
truncate: true
truncate: true,
maxWidthChars: 25,
} as Widget.LabelProps),
new Widget.Label({
className: "artist",
tooltipText: bind(players[0], "artist").as((artist: string) => !artist ? "No Artist" : artist),
label: bind(players[0], "artist").as((artist: string) => !artist ? "No Artist" : artist),
truncate: true
maxWidthChars: 28,
truncate: true,
} as Widget.LabelProps)
]
} as Widget.BoxProps),
@@ -101,6 +102,15 @@ export const BigMedia: Gtk.Widget = new Widget.Box({
players[0].get_meta("xesam:url") === null),
onClick: () => Process.exec(`wl-copy ${players[0].get_meta("xesam:url")?.get_string()[0]}`)
} as Widget.ButtonProps),
new Widget.Button({
className: "shuffle nf",
visible: bind(players[0], "shuffleStatus").as((shuffleStatus: AstalMpris.Shuffle) =>
shuffleStatus !== AstalMpris.Shuffle.UNSUPPORTED),
label: bind(players[0], "shuffleStatus").as((shuffleStatus: AstalMpris.Shuffle) =>
shuffleStatus === AstalMpris.Shuffle.ON ? "󰒝" : "󰒞"),
tooltipText: "Toggle Shuffle",
onClick: () => players[0].shuffle()
} as Widget.ButtonProps),
new Widget.Button({
className: "previous nf",
label: "󰒮",
@@ -125,6 +135,20 @@ export const BigMedia: Gtk.Widget = new Widget.Box({
label: "󰒭",
tooltipText: "Next",
onClick: () => players[0].canGoNext && players[0].next()
} as Widget.ButtonProps),
new Widget.Button({
className: "repeat nf",
visible: bind(players[0], "loopStatus").as((loopStatus: AstalMpris.Loop) =>
loopStatus !== AstalMpris.Loop.UNSUPPORTED),
label: bind(players[0], "loopStatus").as((loopStatus: AstalMpris.Loop) => {
switch(loopStatus) {
case AstalMpris.Loop.TRACK: return "󰑘";
case AstalMpris.Loop.PLAYLIST: return "󰑖";
default: return "󰑗";
}
}),
tooltipText: "Toggle Loop",
onClick: () => players[0].loop()
} as Widget.ButtonProps)
]
} as Widget.BoxProps),
@@ -142,3 +166,24 @@ export const BigMedia: Gtk.Widget = new Widget.Box({
})
])
} as Widget.BoxProps);
/**
* This function handles album art/cover of playing media. If a file is provided
* by the player, it adds the "file://" uri as a prefix, so you can use it in css.
*
* @param player the player you want to pull album art from
* @returns Binding to player.artUrl containing the album art uri, or an undefined binding ig none was found.
* */
function getAlbumArt(player: AstalMpris.Player): Binding<string|undefined> {
return bind(player, "artUrl").as((artUrl: string) => {
const finalUrl: string = artUrl;
if(/^(https|http)$/.test(finalUrl.split("://")[0]))
return artUrl;
else if(artUrl.startsWith("/"))
return "file://" + artUrl;
return undefined;
});
}
+40
View File
@@ -0,0 +1,40 @@
import { timeout, Variable } from "astal";
import { Gtk, Widget } from "astal/gtk3";
const empty = new Widget.Box();
const page = new Variable<Gtk.Widget>(empty);
let connectionId: (number|undefined);
export const PagesWidget: Widget.Revealer = new Widget.Revealer({
revealChild: false,
transitionType: Gtk.RevealerTransitionType.SLIDE_DOWN,
transitionDuration: 250,
child: page()
} as Widget.RevealerProps);
export function showPages(child: Gtk.Widget, onShow?: (self: Widget.Revealer) => void): void {
page.set(child);
PagesWidget.set_reveal_child(true);
connectionId !== undefined && PagesWidget.disconnect(connectionId);
connectionId = PagesWidget.connect("show", (_) =>
onShow && onShow(_));
}
export function getPage(): (Gtk.Widget|null) {
return page.get();
}
export function togglePage(page: Gtk.Widget): void {
PagesWidget.revealChild ?
hidePages()
: showPages(page);
}
export function hidePages(onHide?: () => void) {
PagesWidget.set_reveal_child(false);
console.log("heyyyyy");
timeout(300, () => {
page.set(empty);
onHide && onHide();
});
}
+2 -5
View File
@@ -1,4 +1,4 @@
import { Process, Variable } from "astal";
import { execAsync, Process, Variable } from "astal";
import { Gtk, Widget } from "astal/gtk3";
import AstalHyprland from "gi://AstalHyprland";
@@ -53,10 +53,7 @@ function LogoutButton(): Widget.Button {
return new Widget.Button({
className: "nf",
label: "󰗽",
onClick: () => Process.exec_async(
"bash -c 'wlogout -b 5'",
() => {}
)
onClick: () => execAsync("astal open logout-menu")
} as Widget.ButtonProps);
}
+17 -8
View File
@@ -1,21 +1,30 @@
import { Gtk, Widget } from "astal/gtk3";
import { TileInternet } from "./tiles/Internet";
import { TileBluetooth } from "./tiles/Bluetooth";
export const tileList: Array<Gtk.Widget> = [];
export const tileList: Array<any> = [
TileInternet,
TileBluetooth
];
export function TilesWidget(): Gtk.Widget {
const tilesFlowBox: Gtk.FlowBox = new Gtk.FlowBox({
visible: true,
noShowAll: false,
orientation: Gtk.Orientation.HORIZONTAL
} as Gtk.Grid.ConstructorProps);
orientation: Gtk.Orientation.HORIZONTAL,
rowSpacing: 6,
columnSpacing: 6,
minChildrenPerLine: 2,
maxChildrenPerLine: 2,
expand: true,
homogeneous: true,
} as Gtk.FlowBox.ConstructorProps);
tileList.map((item: Gtk.Widget) =>
tileList.map((item: Gtk.Widget) =>
tilesFlowBox.insert(item, -1));
return new Widget.Box({
children: [
tilesFlowBox
]
className: "tiles-container",
child: tilesFlowBox
} as Widget.BoxProps);
}
@@ -0,0 +1,98 @@
import { bind, timeout } from "astal";
import { Gtk, Widget } from "astal/gtk3";
import AstalBluetooth from "gi://AstalBluetooth?version=0.1";
let watchingDevices: boolean = false;
export function BluetoothPage() {
watchNewDevices();
return new Widget.Box({
className: "page bluetooth container",
orientation: Gtk.Orientation.VERTICAL,
hexpand: true,
children: [
new Widget.Box({
className: "header",
children: [
new Widget.Label({
hexpand: true,
className: "title",
label: "Bluetooth",
halign: Gtk.Align.START
} as Widget.LabelProps),
]
} as Widget.BoxProps),
new Widget.Box({
className: "connections",
orientation: Gtk.Orientation.VERTICAL,
expand: true,
children: bind(AstalBluetooth.get_default(), "devices").as((devices: Array<AstalBluetooth.Device>) =>
devices.filter((device: AstalBluetooth.Device) => device.connected
).map((dev: AstalBluetooth.Device) =>
new Widget.Button({
onClick: () => dev.connected ? dev.disconnect_device(null) : dev.connect_device(null),
child: new Widget.Box({
className: "device",
orientation: Gtk.Orientation.HORIZONTAL,
expand: true,
children: [
new Widget.Label({
className: "alias",
halign: Gtk.Align.START,
label: bind(dev, "alias")
} as Widget.LabelProps),
new Widget.Label({
className: "battery",
halign: Gtk.Align.END,
label: bind(dev, "batteryPercentage").as(String)
} as Widget.LabelProps)
]
} as Widget.BoxProps)
} as Widget.ButtonProps)).concat(
devices.filter((device: AstalBluetooth.Device) => !device.connected
).map((dev: AstalBluetooth.Device) =>
new Widget.Button({
onClick: () => dev.connect_device(() => {}),
child: new Widget.Box({
className: "device",
orientation: Gtk.Orientation.HORIZONTAL,
expand: true,
children: [
new Widget.Label({
className: "alias",
halign: Gtk.Align.START,
label: bind(dev, "alias")
} as Widget.LabelProps),
new Widget.Label({
className: "battery",
halign: Gtk.Align.END,
label: bind(dev, "batteryPercentage").as(String)
} as Widget.LabelProps)
]
} as Widget.BoxProps)
} as Widget.ButtonProps))
)
)
} as Widget.BoxProps)
]
} as Widget.BoxProps)
}
function watchNewDevices(): void {
if(watchingDevices) {
timeout(10000, () => {
reloadDevicesList();
watchNewDevices();
});
}
}
function stopDeviceWatch(): void {
watchingDevices = false;
}
function reloadDevicesList(): void {
AstalBluetooth.get_default().adapter.start_discovery();
timeout(5000, () => AstalBluetooth.get_default().adapter.stop_discovery());
}
@@ -0,0 +1,38 @@
import { bind } from "astal";
import { Gtk, Widget } from "astal/gtk3";
import AstalBluetooth from "gi://AstalBluetooth";
export function WifiPage() {
return new Widget.Box({
className: "page bluetooth container",
orientation: Gtk.Orientation.VERTICAL,
hexpand: true,
children: [
new Widget.Box({
className: "connections",
orientation: Gtk.Orientation.VERTICAL,
expand: true,
children: bind(AstalBluetooth.get_default(), "devices").as((devices: Array<AstalBluetooth.Device>) =>
devices && devices.filter((device: AstalBluetooth.Device) => device.connected
).map((dev: AstalBluetooth.Device) =>
new Widget.Box({
className: "device",
orientation: Gtk.Orientation.HORIZONTAL,
expand: true,
children: [
new Widget.Label({
className: "alias",
halign: Gtk.Align.START,
label: bind(dev, "alias")
} as Widget.LabelProps),
new Widget.Label({
className: "battery",
halign: Gtk.Align.END,
} as Widget.LabelProps)
]
} as Widget.BoxProps)
))
} as Widget.BoxProps)
]
} as Widget.BoxProps);
}
@@ -0,0 +1,21 @@
import { bind } from "astal";
import { Tile, TileProps } from "./Tile";
import AstalBluetooth from "gi://AstalBluetooth";
import { togglePage } from "../Pages";
import { BluetoothPage } from "../pages/Bluetooth";
export const TileBluetooth = Tile({
title: "Bluetooth",
description: bind(AstalBluetooth.get_default(), "devices").as((devices: Array<AstalBluetooth.Device>) => {
const connected: Array<AstalBluetooth.Device> = devices.filter(
(dev: AstalBluetooth.Device) => dev.connected);
return connected[0] ? connected[0].get_alias() : undefined;
}),
onToggledOn: () => AstalBluetooth.get_default().adapter.set_powered(true),
onToggledOff: () => AstalBluetooth.get_default().adapter.set_powered(false),
onClickMore: () => togglePage(BluetoothPage()),
icon: "󰂯",
iconSize: 16,
toggleState: bind(AstalBluetooth.get_default().adapter, "powered")
} as TileProps);
+23 -5
View File
@@ -1,8 +1,26 @@
import { Gtk, Widget } from "astal/gtk3";
import { bind, execAsync } from "astal";
import { Tile, TileProps } from "./Tile";
import AstalNetwork from "gi://AstalNetwork";
import { Widget } from "astal/gtk3";
export const TileInternet = new Widget.Box({
className: "tile more internet",
children: [
toggleButton
]
child: bind(AstalNetwork.get_default(), "wired").as((wired: AstalNetwork.Wired) => Tile({
title: "Wired",
description: bind(wired, "internet").as((internet: AstalNetwork.Internet) => {
switch(internet) {
case AstalNetwork.Internet.CONNECTED:
return "Connected";
case AstalNetwork.Internet.DISCONNECTED:
return "Disconnected";
case AstalNetwork.Internet.CONNECTING:
return "Connecting...";
}
}),
onToggledOn: () => execAsync("nmcli n on"),
onToggledOff: () => execAsync("nmcli n off"),
icon: "󰛳",
iconSize: 16,
toggleState: bind(wired, "internet").as((internet: AstalNetwork.Internet) =>
internet === AstalNetwork.Internet.CONNECTING || internet === AstalNetwork.Internet.CONNECTED)
} as TileProps))
} as Widget.BoxProps);
+78 -23
View File
@@ -1,39 +1,94 @@
import { Binding } from "astal";
import { Binding, Variable } from "astal";
import { Gtk, Widget } from "astal/gtk3";
export type TileProps = {
className?: string | Binding<string | undefined>;
iconName?: string | Binding<string | undefined>;
icon?: string | Binding<string | undefined>;
visible?: boolean | Binding<boolean | undefined>;
iconSize?: number | Binding<number | undefined>;
title: string | Binding<string>;
title: string | Binding<string | undefined>;
description?: string | Binding<string | undefined>;
defaultToggleState?: boolean;
toggleState?: boolean | Binding<boolean | undefined>;
onToggledOn: () => void;
onToggledOff: () => void;
onClickMore?: () => void;
}
export function Tile(props: TileProps): Widget.Box {
export function Tile(props: TileProps): Widget.EventBox {
const toggled = new Variable<boolean>(props.toggleState instanceof Binding ?
(props.toggleState.get() || false) : (props.toggleState || false));
const toggleButton = new Gtk.ToggleButton();
toggleButton.set_active(props.defaultToggleState || false);
if(props?.toggleState instanceof Binding)
props.toggleState.subscribe(val => toggled.set(val || false))();
const moreButton = new Widget.Button({
className: "more",
visible: props.onClickMore
});
return new Widget.EventBox({
className: toggled().as((state: boolean) =>
state ? "tile-eventbox toggled" : "tile-eventbox"),
expand: true,
onClick: () => {
if(toggled.get()) {
toggled.set(false);
console.log(toggled.get());
props.onToggledOff && props.onToggledOff();
return;
}
return new Widget.Box({
className: (typeof Binding<string | undefined>) === (typeof props.className) ?
(props.className as Binding<string | undefined>).as((clsName: (string|undefined)) =>
`tile ${clsName || ""}`)
:
props.className,
visible: props.visible,
children: [
toggleButton,
moreButton
]
})
toggled.set(true);
props.onToggledOn && props.onToggledOn();
},
child: new Widget.Box({
className: (props.className instanceof Binding) ?
props.className.as((clsName: (string|undefined)) =>
`tile ${clsName || ""}`)
: `tile ${props.className || ""}`,
visible: props.visible,
expand: true,
hexpand: true,
children: [
new Widget.Box({
className: "content",
expand: true,
children: [
new Widget.Label({
className: "icon nf",
label: props.icon || "icon",
css: `.icon { font-size: ${props.iconSize || "12px"} }`
} as Widget.LabelProps),
new Widget.Box({
className: "text",
orientation: Gtk.Orientation.VERTICAL,
vexpand: true,
valign: Gtk.Align.CENTER,
children: [
new Widget.Label({
className: "title",
xalign: 0,
truncate: true,
label: props.title
} as Widget.LabelProps),
new Widget.Label({
className: "description",
visible: props.description,
truncate: true,
xalign: 0,
label: props.description
} as Widget.LabelProps)
]
} as Widget.BoxProps)
]
} as Widget.BoxProps),
new Widget.Button({
className: "more icon",
visible: props.onClickMore !== undefined,
halign: Gtk.Align.END,
image: new Widget.Icon({
icon: "go-next-symbolic",
css: "icon { font-size: 16px; }"
}),
onClick: () => props.onClickMore && props?.onClickMore(),
widthRequest: 32
})
]
})
} as Widget.EventBoxProps)
}