ags(bar,scripts,i18n): added i18n system(wip), changed some bar stuff and started doing control center

This commit is contained in:
retrozinndev
2025-02-04 12:39:25 -03:00
parent 09692bae90
commit b544f4a45b
32 changed files with 980 additions and 141 deletions
+13 -5
View File
@@ -1,14 +1,22 @@
import { App } from "astal/gtk3"
import { Bar } from "./window/Bar";
import { runStyleHandler } from "./scripts/style-handler";
import "./scripts/reload-handler";
//import { monitorPaths } from "./scripts/reload-handler"; // Only for debugging purposes(testing new widgets and stuff)
import { handleArguments } from "./scripts/arg-handler";
runStyleHandler();
export const astalInstanceName = "astal"
App.start({
instanceName: "astal",
instanceName: astalInstanceName || "astal",
requestHandler(request: string, res: (result: any) => void) {
console.log(`[LOG] Arguments received: ${request}`)
res(handleArguments(request));
},
main() {
Bar(0);
console.log(`[LOG] Initialized astal instance as: ${ astalInstanceName || "astal" }`);
console.log(`[LOG] Running Stylesheet handler`);
runStyleHandler();
//console.log(`[LOG] Starting to monitor scripts to automatically reload instance`);
//monitorPaths();
}
});
+53
View File
@@ -0,0 +1,53 @@
//TODO use I18n system >.<
import { GLib } from "astal";
export const i18nKeys = {
"en_US": () => import("./lang/en_US"),
"pt_BR": () => import("./lang/pt_BR")
}
export const languages: Array<string> = (() =>
Object.keys(i18nKeys))()
const defaultLanguage: string = languages[0];
let language: string = defaultLanguage;
export function getSystemLanguage(): string {
const sysLanguage: (string|null|undefined) = GLib.getenv("LANG") || GLib.getenv("LANGUAGE");
if(!sysLanguage) {
console.log(`[WARNING] Couldn't get system language, fallback to default ${defaultLanguage || "en_US"}`);
console.log("[TIP] Please set the LANG or LANGUAGE environment variable");
return defaultLanguage || "en_US";
}
return sysLanguage.split('.')[0];
}
export function setLanguage(lang: keyof typeof i18nKeys): (string|Error) {
languages.map((cur: string) => {
if(cur === lang) {
language = lang;
return lang;
}
});
throw new Error(`[i18n/intl] Couldn't set language: ${lang}`, {
cause: `Language ${lang} not found in languages of type ${typeof languages}`
});
}
export function tr(key: string): (string|undefined) {
let langKeys: Object = i18nKeys[language as keyof typeof i18nKeys];
let result = i18nKeys[language as keyof typeof i18nKeys],
defResult = i18nKeys[defaultLanguage as keyof typeof i18nKeys];
key.split('.').map((keyString: string) => {
result = result[keyString as keyof typeof result];
defResult = defResult[keyString as keyof typeof defResult];
});
return (result as never) || (defResult as never) || undefined;
}
+3
View File
@@ -0,0 +1,3 @@
export default {
"language": "English (United States)",
}
+3
View File
@@ -0,0 +1,3 @@
export default {
"language": "Português (Brasil)"
}
+30
View File
@@ -0,0 +1,30 @@
import AstalApps from "gi://AstalApps";
const astalApps: AstalApps.Apps = new AstalApps.Apps();
let appsList: Array<AstalApps.Application> = astalApps.get_list();
export function getApps(): Array<AstalApps.Application> {
return appsList;
}
export function updateApps(): void {
astalApps.reload();
appsList = astalApps.get_list();
}
export function getAppsByName(appName: string): (Array<AstalApps.Application>|undefined) {
let found: Array<AstalApps.Application> = [];
getApps().map((app: AstalApps.Application) => {
if(app.get_name().trim().toLowerCase() === appName.trim().toLowerCase()
|| (app?.wmClass && app.wmClass.trim().toLowerCase() === appName.trim().toLowerCase()))
found.push(app);
});
return (found.length > 0 ? found : undefined);
}
export function getAppIcon(appName: string): (string|undefined) {
const found: (Array<AstalApps.Application>|undefined) = getAppsByName(appName);
return found ? found[0]?.iconName : undefined;
}
+79
View File
@@ -0,0 +1,79 @@
import { Windows } from "./windows";
import { restartInstance } from "./reload-handler";
export function handleArguments(request: string): any {
const args: Array<string> = request.split(" ");
switch(args[0]) {
case "open":
case "close":
case "toggle":
return handleWindowArgs(args);
case "help":
case "h":
return getHelp(); // stop it, get some help
case "reload":
restartInstance({ log: true, instanceName: "astal" });
return "Reloading instance..."
default:
return "command not found! try checking help";
}
}
// Didn't want to bloat the switch statement, so I just separated it into functions
export function handleWindowArgs(args: Array<string>): string {
const windows = Windows.getDefault().getWindows();
const window = windows[args[1] as never];
if(args[1] == undefined || args[1] == "")
return "Window argument not specified!";
if(!Object.hasOwn(windows, args[1]!))
return `Window "${args[1]}" not found windows list!`
switch(args[0]) {
case "open":
if(!Windows.getDefault().isVisible(window)) {
Windows.getDefault().open(window);
return `Setting visibility of window "${args[1]}" to true`;
}
return `Window is already open, ignored`;
case "close":
if(Windows.getDefault().isVisible(window)) {
Windows.getDefault().close(window);
return `Setting visibility of window "${args[1]}" to false`
}
return `Window is already closed, ignored`
case "toggle":
if(!Windows.getDefault().isVisible(window)) {
Windows.getDefault().open(window);
return `Toggle opening window "${args[1]}"`;
}
Windows.getDefault().close(window);
return `Toggle closing window "${args[1]}"`
}
return "Couldn't handle window management arguments"
}
export function getHelp(): string {
return `Manage Astal Windows and do more stuff. From
retrozinndev's Hyprland Dots, using Astal and AGS by Aylur.
Options:
open [window_name]: sets specified window's visibility to true.
close [window_name]: sets specified window's visibility to false.
toggle [window_name]: toggles visibility of specified window.
reload: creates a new astal instance and removes this one.
help, -h, --help: shows this help message.
2024 (c) retrozinndev's Hyprland-Dots, licensed under the MIT License.
https://github.com/retrozinndev/Hyprland-Dots`
}
+67
View File
@@ -0,0 +1,67 @@
import AstalNotifd from "gi://AstalNotifd";
import { Windows } from "./windows";
import { timeout } from "astal/time";
const notifd: AstalNotifd.Notifd = new AstalNotifd.Notifd({
ignoreTimeout: false,
dontDisturb: false
});
const windows = Windows.getDefault();
export let notifications: Array<AstalNotifd.Notification> = [];
export let notificationHistory: Array<AstalNotifd.Notification> = [];
notifd.connect("notified", (_source: AstalNotifd.Notifd, id: number, _replaced: boolean) => {
windows.isVisible(windows.getWindows().floating_notifications) && windows.open(windows.getWindows().floating_notifications);
addNotification(getNotifd().get_notification(id));
});
function addNotification(notification: AstalNotifd.Notification) {
prependArray(notifications, getNotifd().get_notification(notification.id));
// default timeout if undefined
let notificationTimeout = 4000;
switch(notification.urgency) {
case AstalNotifd.Urgency.LOW:
notificationTimeout = 2000;
break;
case AstalNotifd.Urgency.NORMAL:
notificationTimeout = 4000;
break;
}
notification.urgency !== AstalNotifd.Urgency.CRITICAL && timeout(notificationTimeout, () => {
notificationTimeout--;
if(notificationTimeout === 0) {
removeNotification(notification.id);
addToNotificationHistory(notification);
};
});
}
export function removeNotification(notificationId: number) {
notifications = notifications.filter((notification: AstalNotifd.Notification) =>
notification.id !== notificationId);
}
function addToNotificationHistory(notification: AstalNotifd.Notification) {
prependArray(notificationHistory, notification);
}
export function removeFromNotificationHistory(notificationId: number) {
notifications = notifications.filter((curNotification: AstalNotifd.Notification) =>
curNotification.id !== notificationId);
}
function prependArray(array: Array<any>, item: any) {
let tmpArray = array;
tmpArray.reverse();
tmpArray.push(item);
array = tmpArray.reverse();
}
export function getNotifd(): AstalNotifd.Notifd {
return notifd;
}
+22 -9
View File
@@ -1,17 +1,30 @@
import { monitorFile, Process } from "astal";
import { astalInstanceName } from "../app";
import { getUserDirs } from "./user";
const monitoringPaths = [ "./scripts", "./widget", "./app.ts", "env.d.ts" ];
const monitoringPaths = [ "./scripts", "./window", "./app.ts", "env.d.ts" ];
function restartInstance(instanceName?: string) {
Process.exec_async(`ags run`, () => {});
Process.exec_async(`astal -q ${ instanceName ? instanceName : "astal" }`, () => {});
interface InstanceProps {
instanceName?: string;
log?: boolean;
}
monitoringPaths.map((path: string) => {
export function restartInstance(props: InstanceProps = { instanceName: "astal", log: false }): void {
Process.exec_async(`astal -q ${props.instanceName}`, () => {});
Process.exec_async(`ags run ${ props.log && `--log-file
${ getUserDirs().cache}/ags-${ astalInstanceName || "astal" }.log` }`.replaceAll('\n', ' ').trim(),
() => {}
)
}
export function monitorPaths(): void {
monitoringPaths.map((path: string) => {
monitorFile(
path,
() => {
restartInstance("astal");
}
() => restartInstance({
instanceName: astalInstanceName || "astal",
log: true
})
)
})
});
}
+1 -1
View File
@@ -1,6 +1,6 @@
// handles reloading stylesheet and pywal colors
import { readFile, monitorFile, Process, Gio } from "astal";
import { readFile, monitorFile, Process } from "astal";
import { App } from "astal/gtk3";
import { getUserDirs } from "./user";
+6
View File
@@ -0,0 +1,6 @@
import { GLib, Variable } from "astal";
const time = new Variable<GLib.DateTime>(GLib.DateTime.new_now_local()).poll(600, () =>
GLib.DateTime.new_now_local())();
export const getDateTime = () => time;
+68
View File
@@ -0,0 +1,68 @@
import AstalWp from "gi://AstalWp";
export class Wireplumber {
private astalWireplumber: (AstalWp.Wp|null) = AstalWp.get_default();
private defaultSink: AstalWp.Endpoint = this.astalWireplumber!.get_default_speaker()!;
private defaultSource: AstalWp.Endpoint = this.astalWireplumber!.get_default_microphone()!;
private static inst: Wireplumber = new Wireplumber();
private maxSinkVolume: number = 100;
private maxSourceVolume: number = 100;
constructor() {
if(!this.astalWireplumber)
throw new Error("Audio features will not work correctly! Please install wireplumber first", {
cause: "Wireplumber library not found"
});
}
public static getDefault(): Wireplumber {
return Wireplumber.inst;
}
public getDefaultSink(): AstalWp.Endpoint {
return this.defaultSink;
}
public getDefaultSource(): AstalWp.Endpoint {
return this.defaultSource;
}
public getSinkVolume(): number {
return this.getDefaultSink().get_volume() * 100;
}
public getSourceVolume(): number {
return this.getDefaultSource().get_volume() * 100;
}
public setSinkVolume(newSinkVolume: number) {
this.defaultSink.set_volume(
(newSinkVolume > this.maxSinkVolume ? this.maxSinkVolume : newSinkVolume) / 100
);
}
public setSourceVolume(newSourceVolume: number) {
this.defaultSource.set_volume(
newSourceVolume > this.maxSourceVolume ? this.maxSourceVolume : newSourceVolume / 100
);
}
public increaseSinkVolume(volumeIncrease: number) {
if(volumeIncrease > this.maxSinkVolume
|| (this.maxSinkVolume + volumeIncrease) > this.maxSinkVolume) {
this.setSinkVolume(this.maxSinkVolume);
}
this.setSinkVolume(this.getSinkVolume() + volumeIncrease);
}
public increaseSourceVolume(volumeIncrease: number) {
if(volumeIncrease > this.maxSourceVolume //TODO
|| (this.maxSinkVolume + volumeIncrease) > this.maxSinkVolume) {
this.setSinkVolume(this.maxSinkVolume);
}
this.setSinkVolume(this.getSinkVolume() + volumeIncrease);
}
}
+40
View File
@@ -0,0 +1,40 @@
// get open windows / interact with windows(e.g.: close, open or toggle)
import { Widget } from "astal/gtk3";
import { Bar } from "../window/Bar";
import { OSD } from "../window/OSD";
import { ControlCenter } from "../window/ControlCenter";
//import { FloatingNotifications } from "../window/FloatingNotifications";
export class Windows {
private static inst: Windows = new Windows();
/* Windows List(js object):
* add all windows here */
private readonly windows = {
"bar": Bar,
"osd": OSD,
"control-center": ControlCenter
//"floating-notifications": FloatingNotifications
};
public static getDefault(): Windows {
return Windows.inst;
}
public getWindows(): typeof this.windows {
return this.windows;
}
public open(window: Widget.Window): void {
window.show();
}
public isVisible(window: Widget.Window): boolean {
return window.get_visible();
}
public close(window: Widget.Window): void {
window.hide();
}
}
+2
View File
@@ -1,2 +1,4 @@
@use "./style/bar";
@use "./style/wal";
@use "./style/osd";
@use "./style/control-center"
+40 -6
View File
@@ -41,11 +41,15 @@
}
}
// Style widget groups
& > .bar-centerbox > * {
background: rgba($color: wal.$background, $alpha: .6);
padding: 6px;
padding: 5px;
border-radius: 18px;
// Style widgets
& > *,
& > * > button
& > * {
margin: 0 2px;
@@ -68,17 +72,18 @@
.workspaces {
padding: 2px 2px;
& button {
all: unset;
border-radius: 16px;
transition: 80ms linear;
padding: 0 12px;
padding: 12px 12px;
background: wal.$color1;
margin: 1px 2px;
&.focus {
background: wal.$foreground;
padding: 0 20px;
padding: 12px 20px;
}
}
}
@@ -108,9 +113,8 @@
}
.logo button {
$padding-inline: 12px;
padding-left: $padding-inline;
padding-right: calc($padding-inline + 3.9px);
padding: 0 11px;
padding-right: 16px;
& label {
font-size: 14px;
@@ -128,6 +132,36 @@
font-size: 14px;
}
}
&.reveal {
& .media > box {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
& .media-controls {
padding-left: 3px;
border-top-right-radius: 12px;
border-bottom-right-radius: 12px;
background: scale-color($color: wal.$color3, $lightness: -12%);
& > button {
margin: 0px 1px;
border-radius: 4px;
&:first-child {
border-top-left-radius: 12px;
border-bottom-left-radius: 12px;
margin-left: 0;
}
&:last-child {
border-top-right-radius: 12px;
border-bottom-right-radius: 12px;
margin-right: 0;
}
}
}
}
}
.tray {
+54
View File
@@ -0,0 +1,54 @@
@use "./wal";
.control-center-container {
all: unset;
background: rgba(wal.$background, .6);
border-top-left-radius: 16px;
border-bottom-left-radius: 16px;
margin: 32px 0;
& * {
all: unset;
transition: 120ms linear;
}
& {
& button {
padding: 4px 6px;
background: scale-color($color: wal.$color1, $lightness: -20%);
border-radius: 12px;
}
& .quickactions {
padding: 10px 16px;
& .button-row {
& > button {
margin: 5px 2px;
border-radius: 2px;
padding: 0 8px;
background: rgba($color: scale-color($color: wal.$color1, $lightness: -20%), $alpha: .7);
& label {
font-size: 16px;
}
&:hover {
background: rgba($color: scale-color($color: wal.$color1, $lightness: -20%), $alpha: 1);
}
&:first-child {
margin-left: 0;
border-top-left-radius: 8px;
border-bottom-left-radius: 8px;
}
&:last-child {
margin-right: 0;
border-top-right-radius: 8px;
border-bottom-right-radius: 8px;
}
}
}
}
}
}
+37
View File
@@ -0,0 +1,37 @@
@use "./wal";
.osd-window {
all: unset;
.osd {
margin-bottom: 100px;
background: rgba($color: wal.$background, $alpha: .5);
padding: 14px 16px;
border-radius: 20px;
.icon {
margin-right: 14px;
font-size: 24px;
}
.volume {
margin-top: -6px;
.value {
margin-bottom: 5px;
}
levelbar {
trough block {
border-radius: 6px;
background: wal.$background;
&.filled {
padding: 3px 0;
background: wal.$color1;
}
}
}
}
}
}
+20 -20
View File
@@ -1,26 +1,26 @@
// SCSS Variables
// Generated by 'wal'
$wallpaper: "/home/joaov/wallpapers/Garden Kita.png";
$wallpaper: "/home/joaov/wallpapers/Miku, Rin and Luka Chibi.jpg";
// Special
$background: #101212;
$foreground: #c3c3c3;
$cursor: #c3c3c3;
$background: #3d2217;
$foreground: #cec7c5;
$cursor: #cec7c5;
// Colors
$color0: #101212;
$color1: #59662a;
$color2: #517047;
$color3: #87863c;
$color4: #707b48;
$color5: #4b6266;
$color6: #84876e;
$color7: #8e9898;
$color8: #596d6d;
$color9: #778839;
$color10: #6C965F;
$color11: #B4B350;
$color12: #96A460;
$color13: #658388;
$color14: #B0B493;
$color15: #c3c3c3;
$color0: #3d2217;
$color1: #b38678;
$color2: #a4998a;
$color3: #b39e8a;
$color4: #a5a09b;
$color5: #aea299;
$color6: #b4aea2;
$color7: #a39c99;
$color8: #7f6f68;
$color9: #EFB3A1;
$color10: #DBCCB9;
$color11: #EFD3B9;
$color12: #DDD6CF;
$color13: #E8D8CD;
$color14: #F1E8D9;
$color15: #cec7c5;
+4 -4
View File
@@ -12,15 +12,15 @@ export function Separator(props: SeparatorProps) {
return new Widget.Box({
className: `separator separator-${ props.orientation == Gtk.Orientation.VERTICAL ? "vertical" : "horizontal" } ${ props.class && props.class }`,
css: `.separator {
background: ${ props.cssColor ? props.cssColor : "lightgray" };
opacity: ${ props.alpha ? props.alpha : 1 };
background: ${ props.cssColor || "lightgray" };
opacity: ${ props.alpha || 1 };
}
.separator-horizontal {
padding-right: ${props.size ? props.size : 1 }px;
padding-right: ${props.size || 1 }px;
margin: 7px 4px;
}
.separator-vertical {
padding-bottom: ${props.size ? props.size : 1 }px;
padding-bottom: ${props.size || 1 }px;
margin: 4px 7px;
}`,
} as Widget.BoxProps);
+4 -2
View File
@@ -19,7 +19,8 @@ export function Audio() {
} as Widget.LabelProps),
new Widget.Label({
className: "icon nf",
label: bind(wp!.defaultSpeaker, "volume").as((volume: number) => Math.round(volume * 100).toString() + "%")
label: bind(wp!.defaultSpeaker, "volume").as((volume: number) =>
Math.round(volume * 100).toString() + "%")
} as Widget.LabelProps)
]
})
@@ -33,7 +34,8 @@ export function Audio() {
label: "󰍬"
} as Widget.LabelProps),
new Widget.Label({
label: bind(wp!.defaultMicrophone, "volume").as((volume: number) => Math.round(volume * 100).toString() + "%")
label: bind(wp!.defaultMicrophone, "volume").as((volume: number) =>
Math.round(volume * 100).toString() + "%")
} as Widget.LabelProps)
]
})
+5 -7
View File
@@ -1,16 +1,14 @@
import { Box, Button } from "astal/gtk3/widget";
import { GLib, Variable } from "astal";
import { Widget } from "astal/gtk3";
const dateTimeFormat = "%A %d, %H:%M"
const time = new Variable<string>("").poll(600, () =>
GLib.DateTime.new_now_local().format(dateTimeFormat)!);
import { getDateTime } from "../../scripts/time";
import { GLib } from "astal";
export function Clock(): JSX.Element {
return new Widget.Box({
className: "clock",
child: new Widget.Button({
label: time()
label: getDateTime().as((dateTime: GLib.DateTime) => {
return dateTime.format("%A %d, %H:%M")
})
} as Widget.ButtonProps)
} as Widget.BoxProps);
}
+7 -10
View File
@@ -1,6 +1,7 @@
import { bind } from "astal";
import { Gtk, Widget } from "astal/gtk3";
import AstalHyprland from "gi://AstalHyprland";
import { getAppIcon } from "../../scripts/apps";
const hyprland = AstalHyprland.get_default();
@@ -11,14 +12,8 @@ export function FocusedWindow() {
children: [
new Widget.Icon({
className: "icon",
icon: bind(hyprland, "focusedClient").as(Boolean) && bind(hyprland, "focusedClient").as((client: AstalHyprland.Client) => {
switch(client.initialClass) {
case "zen":
return "zen-browser";
default:
return client.initialClass;
}}),
icon: bind(hyprland, "focusedClient").as((client: AstalHyprland.Client) =>
getAppIcon(client.initialClass) || "image-missing"),
iconSize: Gtk.IconSize.SMALL_TOOLBAR
}),
new Widget.Box({
@@ -29,12 +24,14 @@ export function FocusedWindow() {
new Widget.Label({
className: "class",
xalign: 0,
label: bind(hyprland, "focusedClient").as(Boolean) && bind(hyprland, "focusedClient").as((client: AstalHyprland.Client) => client.get_class())
label: bind(hyprland, "focusedClient").as((client: AstalHyprland.Client) =>
client?.["class"])
} as Widget.LabelProps),
new Widget.Label({
className: "title",
xalign: 0,
label: bind(hyprland, "focusedClient").as(Boolean) && bind(hyprland, "focusedClient").as((client: AstalHyprland.Client) => client.get_title())
label: bind(hyprland, "focusedClient").as((client: AstalHyprland.Client) =>
client?.["title"])
} as Widget.LabelProps)
]
})
+2 -2
View File
@@ -1,10 +1,10 @@
import { Box, Button } from "astal/gtk3/widget";
import { Process } from "astal";
import AstalHyprland from "gi://AstalHyprland";
export function Logo() {
return (
<Box className={"logo"}>
<Button onClick={ () => Process.exec("hyprctl dispatch exec anyrun") } label={""} />
<Button onClick={ () => AstalHyprland.get_default().dispatch("exec", "anyrun") } label={""} />
</Box>
)
}
+54 -8
View File
@@ -2,7 +2,6 @@ import { bind } from "astal";
import { Gtk, Widget } from "astal/gtk3";
import AstalMpris from "gi://AstalMpris";
import { Separator, SeparatorProps } from "../Separator";
import { Wal } from "../../scripts/pywal";
const mpris: AstalMpris.Mpris = AstalMpris.get_default();
let defaultPlayer: (AstalMpris.Player|undefined) = mpris.get_players()?.[0];
@@ -10,6 +9,7 @@ let defaultPlayer: (AstalMpris.Player|undefined) = mpris.get_players()?.[0];
const playerIcons = {
spotify: '󰓇',
clapper: '󰿎',
mpv: '',
spotube: '󰋋',
firefox: '󰈹'
}
@@ -20,7 +20,46 @@ export function Media(): JSX.Element {
defaultPlayer = players?.[0] as AstalMpris.Player;
});
return new Widget.EventBox({
const mediaControlsRevealer: Widget.Revealer = new Widget.Revealer({
transitionType: Gtk.RevealerTransitionType.SLIDE_RIGHT,
transitionDuration: 260,
revealChild: false,
child: new Widget.Box({
className: "media-controls",
homogeneous: false,
children: [
new Widget.Button({
className: "previous",
label: "󰒮",
onClick: () => {
if(bind(defaultPlayer!, "canGoPrevious").as(Boolean))
defaultPlayer?.previous();
}
} as Widget.ButtonProps),
new Widget.Button({
className: "pause",
label: bind(defaultPlayer!, "playback_status").as((status: AstalMpris.PlaybackStatus) => {
return status === AstalMpris.PlaybackStatus.PLAYING ? "󰏤" : "󰐊"
}),
onClick: () => {
if(bind(defaultPlayer!, "canPlay").as(Boolean)
|| bind(defaultPlayer!, "canPause").as(Boolean))
defaultPlayer?.play_pause();
}
} as Widget.ButtonProps),
new Widget.Button({
className: "next",
label: "󰒭",
onClick: () => {
if(bind(defaultPlayer!, "canGoNext").as(Boolean))
defaultPlayer?.next();
}
} as Widget.ButtonProps)
]
} as Widget.BoxProps)
} as Widget.RevealerProps);
const mediaWidget = new Widget.EventBox({
className: "media-eventbox",
visible: bind(mpris, "players").as((players: Array<AstalMpris.Player>) => players?.[0]).as(Boolean),
child: new Widget.Box({
@@ -42,7 +81,7 @@ export function Media(): JSX.Element {
} as Widget.LabelProps),
Separator({
size: 2,
cssColor: `rgb(150, 150, 150)`,
cssColor: `rgb(180, 180, 180)`,
alpha: 1
} as SeparatorProps),
new Widget.Label({
@@ -51,12 +90,19 @@ export function Media(): JSX.Element {
} as Widget.LabelProps)
]
} as Widget.BoxProps),
new Widget.Revealer({
transitionType: Gtk.RevealerTransitionType.SLIDE_RIGHT,
transitionDuration: 400,
revealChild: false //FIXME
} as Widget.RevealerProps)
mediaControlsRevealer
]
} as Widget.BoxProps)
} as Widget.EventBoxProps);
mediaWidget.connect("hover", () => {
mediaControlsRevealer.set_reveal_child(true);
mediaWidget.className = mediaWidget.className + " reveal";
});
mediaWidget.connect("hover-lost", () => {
mediaControlsRevealer.set_reveal_child(false);
mediaWidget.className = mediaWidget.className.replaceAll(" reveal", "");
})
return mediaWidget;
}
+4 -1
View File
@@ -1,5 +1,5 @@
import { bind } from "astal";
import { Astal, Gtk, Widget } from "astal/gtk3";
import { Gtk, Widget } from "astal/gtk3";
import AstalTray from "gi://AstalTray"
const astalTray = AstalTray.get_default();
@@ -7,6 +7,7 @@ const astalTray = AstalTray.get_default();
export function Tray() {
return new Widget.Box({
className: "tray",
visible: bind(astalTray, "items").as((items: Array<AstalTray.TrayItem>) => items.length > 0),
children: bind(astalTray, "items").as((items: Array<AstalTray.TrayItem>) =>
items.map((item: AstalTray.TrayItem) =>
new Widget.MenuButton({
@@ -15,6 +16,8 @@ export function Tray() {
menuModel: bind(item, "menuModel"),
usePopover: false,
actionGroup: bind(item, "actionGroup").as((actionGroup: any) => ["dbusmenu", actionGroup]),
direction: Gtk.ArrowType.DOWN,
halign: Gtk.Align.CENTER,
child: new Widget.Icon({
gIcon: bind(item, "gicon"),
iconSize: Gtk.IconSize.SMALL_TOOLBAR
+18 -8
View File
@@ -1,21 +1,31 @@
import { bind } from "astal";
import { Widget } from "astal/gtk3";
import { Gdk, Gtk, Widget } from "astal/gtk3";
import AstalHyprland from "gi://AstalHyprland";
const hyprland = AstalHyprland.get_default();
export function Workspaces() {
return new Widget.Box({
const workspacesEventBox = new Widget.EventBox({
onScroll: (_, event) =>
event.delta_y > 0 ? hyprland.dispatch("workspace", "e-1") : hyprland.dispatch("workspace", "e+1"),
child: new Widget.Box({
className: "workspaces",
children: bind(hyprland, "workspaces").as((workspaces) =>
workspaces.sort((a: AstalHyprland.Workspace, b: AstalHyprland.Workspace) =>
a.get_id() - b.get_id())
.map((workspace: AstalHyprland.Workspace) =>
vexpand: false,
valign: Gtk.Align.CENTER,
children: bind(hyprland, "workspaces").as((workspaces) => {
const sortedWorkspaces = workspaces.sort((a: AstalHyprland.Workspace, b: AstalHyprland.Workspace) => a.get_id() - b.get_id());
return sortedWorkspaces.map((workspace: AstalHyprland.Workspace) =>
new Widget.Button({
className: bind(hyprland, "focusedWorkspace").as((focusedWs: AstalHyprland.Workspace) => workspace === focusedWs ? "focus" : ""),
visible: true,
onClicked: () => workspace.focus()
} as Widget.ButtonProps)
)
)
} as Widget.BoxProps);
})
} as Widget.BoxProps)
} as Widget.EventBoxProps);
return workspacesEventBox;
}
+30
View File
@@ -0,0 +1,30 @@
import { Gtk, Widget } from "astal/gtk3";
export function ButtonGrid(): Widget.Box {
return new Widget.Box({
child: new Gtk.Grid({
orientation: Gtk.Orientation.HORIZONTAL,
rowHomogeneous: true
} as Gtk.Grid.ConstructorProps, BluetoothToggle())
} as Widget.BoxProps);
}
// Buttons and Toggles!
export function BluetoothToggle(): Gtk.ToggleButton {
return new Gtk.ToggleButton({
child: new Widget.Box({
orientation: Gtk.Orientation.VERTICAL,
children: [
new Widget.Label({
className: "title",
label: "Bluetooth"
} as Widget.LabelProps),
new Widget.Label({
className: "extra",
label: "[dev] [dev_bat]"
} as Widget.LabelProps)
]
} as Widget.BoxProps)
});
}
+95
View File
@@ -0,0 +1,95 @@
import { Process, Variable } from "astal";
import { Gtk, Widget } from "astal/gtk3";
import AstalHyprland from "gi://AstalHyprland";
const hostname: string = Process.exec("cat /etc/hostname") || "GNU/Linux";
const uptime = new Variable<string>("Just turned on")
.poll(1000, () => {
return Process.exec("uptime -p").replace(/^up /, "")
})();
const quickActionsBox: Widget.Box = new Widget.Box({
className: "quickactions",
hexpand: true,
children: [
new Widget.Box({
orientation: Gtk.Orientation.VERTICAL,
halign: Gtk.Align.START,
children: [
new Widget.Label({
className: "hostname",
xalign: 0,
label: hostname.toString()
} as Widget.LabelProps),
new Widget.Label({
className: "uptime",
xalign: 0,
label: uptime.as((uptime: string) => `󱡢 ${uptime}`)
} as Widget.LabelProps)
]
} as Widget.BoxProps),
new Widget.Box({
orientation: Gtk.Orientation.HORIZONTAL,
className: "button-row",
halign: Gtk.Align.END,
children: [
LockButton(),
ColorPickerButton(),
ScreenshotButton(),
SelectWallpaperButton(),
LogoutButton()
]
} as Widget.BoxProps)
]
} as Widget.BoxProps);
export function QuickActionsWidget(): Widget.Box {
return quickActionsBox;
}
function LockButton(): Widget.Button {
return new Widget.Button({
label: "󰌾",
onClick: () => AstalHyprland.get_default().dispatch("exec", "hyprlock")
} as Widget.ButtonProps)
}
function ColorPickerButton(): Widget.Button {
return new Widget.Button({
label: "󰴱",
onClick: () => AstalHyprland.get_default().dispatch(
"exec",
"sh $HOME/.config/eww/scripts/color-picker.sh"
)
} as Widget.ButtonProps)
}
function ScreenshotButton(): Widget.Button {
return new Widget.Button({
label: "󰹑",
onClick: () => Process.exec_async(
"bash -c 'hyprshot -m region -o $HOME/Screenshots'",
() => {}
)
} as Widget.ButtonProps);
}
function SelectWallpaperButton(): Widget.Button {
return new Widget.Button({
label: "󰸉",
onClick: () => Process.exec_async(
"bash -c 'sh $HOME/.config/hypr/scripts/change-wallpaper.sh'",
() => {}
)
} as Widget.ButtonProps);
}
function LogoutButton(): Widget.Button {
return new Widget.Button({
label: "󰗽",
onClick: () => Process.exec_async(
"bash -c 'loginctl terminate-user $USER'",
() => {}
)
} as Widget.ButtonProps);
}
+74
View File
@@ -0,0 +1,74 @@
import { Gdk, Astal, Gtk, Widget } from "astal/gtk3";
import { Clock } from "../widget/bar/Clock";
import { Logo } from "../widget/bar/Logo";
import { CCToggle } from "../widget/bar/CCToggle";
import { Tray } from "../widget/bar/Tray";
import { Workspaces } from "../widget/bar/Workspaces";
import { Audio } from "../widget/bar/Audio";
import { FocusedWindow } from "../widget/bar/FocusedWindow";
//import { Media } from "../widget/bar/Media";
interface BarProps {
monitor: number;
width?: number;
height?: number;
}
export const Bar: Widget.Window = newBar({
monitor: 0
} as BarProps);
function newBar(props: BarProps): Widget.Window {
return new Widget.Window({
className: "bar",
monitor: props.monitor,
namespace: "top-bar",
anchor: Astal.WindowAnchor.TOP,
layer: Astal.Layer.TOP,
exclusivity: Astal.Exclusivity.EXCLUSIVE,
canFocus: false,
visible: true, // Recommendation: set visible to false if you don't want this window to appear on app start
heightRequest: props.height || 0,
widthRequest: props.width || Gdk.Screen.get_default()?.get_monitor_geometry(props.monitor)?.width,
hexpand: false,
vexpand: false,
child: new Widget.Box({
className: "bar-container",
child: new Widget.CenterBox({
className: "bar-centerbox",
expand: true,
homogeneous: false,
startWidget: new Widget.Box({
className: "widgets-left",
homogeneous: false,
halign: Gtk.Align.START,
children: [
Logo(),
Workspaces(),
FocusedWindow()
]
} as Widget.BoxProps),
centerWidget: new Widget.Box({
className: "widgets-center",
homogeneous: false,
halign: Gtk.Align.CENTER,
children: [
Clock(),
/*<Media />*/
]
} as Widget.BoxProps),
endWidget: new Widget.Box({
className: "widgets-right",
homogeneous: false,
halign: Gtk.Align.END,
children: [
Tray(),
Audio(),
CCToggle()
]
} as Widget.BoxProps)
} as Widget.CenterBoxProps)
} as Widget.BoxProps)
} as Widget.WindowProps);
}
-50
View File
@@ -1,50 +0,0 @@
import { Box, CenterBox } from "astal/gtk3/widget";
import { Astal, Gtk } from "astal/gtk3";
import Gdk from "gi://Gdk?version=3.0";
import { Clock } from "../widget/bar/Clock";
import { Logo } from "../widget/bar/Logo";
import { CCToggle } from "../widget/bar/CCToggle";
import { Tray } from "../widget/bar/Tray";
import { Workspaces } from "../widget/bar/Workspaces";
import { Audio } from "../widget/bar/Audio";
import { FocusedWindow } from "../widget/bar/FocusedWindow";
import { Media } from "../widget/bar/Media";
export function Bar(monitor: number = 0, width: (number|undefined) = undefined, height: (number|undefined) = undefined) {
return (
<window className="bar" monitor={ monitor } namespace={ "top-bar" }
anchor={ Astal.WindowAnchor.TOP } layer={ Astal.Layer.TOP }
exclusivity={ Astal.Exclusivity.EXCLUSIVE } canFocus={ false }
heightRequest={ height ? height : 0 }
widthRequest={ width ? width : Gdk.Screen.get_default()?.get_monitor_geometry(monitor)?.width }>
<Box className={ "bar-container" } spacing={ 2 }>
<CenterBox className={ "bar-centerbox" } expand={ true }>
<Box className={ "widgets-left" } vertical={ false }
homogeneous={ false } halign={ Gtk.Align.START }>
<Logo />
<Workspaces />
<FocusedWindow />
</Box>
<Box className={ "widgets-center" } halign={ Gtk.Align.CENTER }
vertical={ false } homogeneous={ false }>
<Clock />
<Media />
</Box>
<Box className={ "widgets-right" } halign={ Gtk.Align.END }
vertical={ false } homogeneous={ false }>
<Tray />
<Audio />
<CCToggle />
</Box>
</CenterBox>
</Box>
</window>
)
}
+31
View File
@@ -0,0 +1,31 @@
import { Astal, Gdk, Gtk, Widget } from "astal/gtk3";
import { QuickActionsWidget } from "../widget/control-center/QuickActions";
export const ControlCenter: Widget.Window = CC();
export const widgetsBox: Widget.Box = new Widget.Box({
visible: true,
className: "control-center-container",
orientation: Gtk.Orientation.VERTICAL,
children: [
QuickActionsWidget()
]
} as Widget.BoxProps);
widgetsBox.connect("add", (_: Widget.Box, widget: Gtk.Widget) => {
widget.set_size_request(widgetsBox.get_allocated_width(), widget.get_allocated_height());
});
function CC(): Widget.Window {
return new Widget.Window({
className: "control-center",
namespace: "control-center",
canFocus: true,
exclusivity: Astal.Exclusivity.NORMAL,
anchor: Astal.WindowAnchor.RIGHT,
width_request: 450,
height_request: Gdk.Screen.get_default()?.get_monitor_geometry(0)?.height || 800,
monitor: 0,
visible: false,
child: widgetsBox
} as Widget.WindowProps);
}
+58
View File
@@ -0,0 +1,58 @@
import { Astal, Gtk, Widget } from "astal/gtk3";
import { getNotifd, removeNotification } from "../scripts/notification-handler";
import { notifications as popupNotifications } from "../scripts/notification-handler";
import AstalNotifd from "gi://AstalNotifd";
export const FloatingNotifications: Widget.Window = FloatingNotificationsWindow();
let gtkNotificationPopups: Array<Widget.Box> = [];
function FloatingNotificationsWindow(): Widget.Window {
const notificationsBox = new Widget.Box({
className: "notifications",
orientation: Gtk.Orientation.VERTICAL,
homogeneous: false
} as Widget.BoxProps);
getNotifd().connect("notified", () => {
for(let i = 0; i < popupNotifications.length; i++) {
const notification: AstalNotifd.Notification = popupNotifications[i];
gtkNotificationPopups[i] = new Widget.Box({
className: "notification",
homogeneous: false,
children: [
new Widget.Box({
className: "top",
orientation: Gtk.Orientation.HORIZONTAL,
hexpand: true,
vexpand: false,
children: [
new Widget.Label({
className: "app-name",
halign: Gtk.Align.START,
label: notification.appName || "Unknown Application"
} as Widget.LabelProps),
new Widget.Button({
className: "close-button",
onClick: () => removeNotification(notification.id)
} as Widget.ButtonProps)
]
} as Widget.BoxProps)
]
} as Widget.BoxProps);
}
})
return new Widget.Window({
className: "window floating-notifications",
namespace: "floating-notifications",
canFocus: false,
anchor: Astal.WindowAnchor.RIGHT,
monitor: 0,
layer: Astal.Layer.OVERLAY,
visible: false,
exclusivity: Astal.Exclusivity.NORMAL,
child: notificationsBox
} as Widget.WindowProps);
}
+48
View File
@@ -0,0 +1,48 @@
import { bind } from "astal";
import { Astal, Gtk, Widget } from "astal/gtk3";
import { Time } from "astal/time";
import AstalWp from "gi://AstalWp";
import { Windows } from "../scripts/windows";
export const OSD: Widget.Window = OSDWindow();
function OSDWindow() {
return new Widget.Window({
className: "osd-window",
namespace: "osd",
layer: Astal.Layer.OVERLAY,
anchor: Astal.WindowAnchor.BOTTOM,
canFocus: false,
monitor: 0,
visible: false,
child: new Widget.Box({
className: "osd",
children: [
new Widget.Label({
className: "icon",
label: "󰕾",
css: ".icon { color: white; }"
} as Widget.LabelProps),
new Widget.Box({
className: "volume",
orientation: Gtk.Orientation.VERTICAL,
valign: Gtk.Align.CENTER,
children: [
new Widget.Label({
className: "value",
label: bind(AstalWp.get_default()?.defaultSpeaker!, "volume").as((volume: number) => `${Math.round(volume * 100)}%`),
halign: Gtk.Align.CENTER
} as Widget.LabelProps),
new Widget.LevelBar({
className: "levelbar",
width_request: 120,
value: bind(AstalWp.get_default()?.defaultSpeaker!, "volume").as((volume: number) => Math.round(volume * 100)),
maxValue: 100,
halign: Gtk.Align.CENTER
} as Widget.LevelBarProps)
]
} as Widget.BoxProps)
]
} as Widget.BoxProps)
} as Widget.WindowProps);
}