ags(bar,scripts,i18n): added i18n system(wip), changed some bar stuff and started doing control center
This commit is contained in:
+13
-5
@@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default {
|
||||
"language": "English (United States)",
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default {
|
||||
"language": "Português (Brasil)"
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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`
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,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";
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,4 @@
|
||||
@use "./style/bar";
|
||||
@use "./style/wal";
|
||||
@use "./style/osd";
|
||||
@use "./style/control-center"
|
||||
|
||||
+40
-6
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
]
|
||||
})
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
]
|
||||
})
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user