ags: make osd work, new window management system, lots of improvements

This commit is contained in:
retrozinndev
2025-02-07 16:02:58 -03:00
parent 0bd0b53589
commit b0bd785ddd
24 changed files with 559 additions and 254 deletions
+27 -15
View File
@@ -1,36 +1,48 @@
import { App } from "astal/gtk3" import { App } from "astal/gtk3"
import { Bar } from "./window/Bar";
import { OSD } from "./window/OSD"; import { OSD, OSDModes, setOSDMode } from "./window/OSD";
import { ControlCenter } from "./window/ControlCenter"; import { ControlCenter } from "./window/ControlCenter";
import { runStyleHandler } from "./scripts/style-handler"; import { runStyleHandler } from "./scripts/style-handler";
import { handleArguments } from "./scripts/arg-handler"; import { handleArguments } from "./scripts/arg-handler";
import { monitorPaths } from "./scripts/reload-handler"; import { Wireplumber } from "./scripts/volume";
import { Windows } from "./windows";
import { Time, timeout } from "astal/time";
let osdTimer: (Time|undefined);
export const astalInstanceName = "astal"
App.start({ App.start({
instanceName: astalInstanceName || "astal", instanceName: "astal",
requestHandler(request: string, res: (result: any) => void) { requestHandler(request: string, res: (result: any) => void) {
console.log(`[LOG] Arguments received: ${request}`) console.log(`[LOG] Arguments received: ${request}`)
res(handleArguments(request)); res(handleArguments(request));
}, },
main() { main() {
console.log(`[LOG] Initialized astal instance as: ${ astalInstanceName || "astal" }`); console.log(`[LOG] Initialized astal instance as: ${ App.instanceName || "astal" }`);
console.log(`[LOG] Running Stylesheet handler`); console.log(`[LOG] Running Stylesheet handler`);
runStyleHandler(); runStyleHandler();
//console.log(`[LOG] Starting to monitor scripts to automatically reload instance`); //console.log(`[LOG] Starting to monitor scripts to automatically reload instance`);
//monitorPaths(); // Only for debugging purposes(testing new widgets and stuff) //monitorPaths(); // Only for debugging purposes(testing new widgets and stuff)
Wireplumber.getDefault().getDefaultSink().connect("notify::volume", () =>
!Windows.isVisible(ControlCenter) && triggerOSD(OSDModes.SINK));
} }
}); });
// Windows list function triggerOSD(osdModeParam: OSDModes) {
export function getWindowsMap(): Object { setOSDMode(osdModeParam);
return {
"bar": Bar, Windows.open(OSD);
"osd": OSD, if(!osdTimer) {
"control-center": ControlCenter, osdTimer = timeout(3000, () => {
//"floating-notifications": FloatingNotifications Windows.close(OSD);
}; osdTimer = undefined;
});
} else {
osdTimer.cancel();
osdTimer = timeout(3000, () => {
Windows.close(OSD);
osdTimer = undefined;
});
}
} }
+56 -18
View File
@@ -1,5 +1,7 @@
import { Windows } from "./windows"; import { Gtk } from "astal/gtk3";
import { Windows } from "../windows";
import { restartInstance } from "./reload-handler"; import { restartInstance } from "./reload-handler";
import { Wireplumber } from "./volume";
export function handleArguments(request: string): any { export function handleArguments(request: string): any {
const args: Array<string> = request.split(" "); const args: Array<string> = request.split(" ");
@@ -13,8 +15,11 @@ export function handleArguments(request: string): any {
case "h": case "h":
return getHelp(); // stop it, get some help return getHelp(); // stop it, get some help
case "volume":
return handleVolumeArgs(args);
case "reload": case "reload":
restartInstance({ log: true, instanceName: "astal" }); restartInstance({ log: false, instanceName: "astal" });
return "Reloading instance..." return "Reloading instance..."
default: default:
@@ -23,48 +28,80 @@ export function handleArguments(request: string): any {
} }
// Didn't want to bloat the switch statement, so I just separated it into functions // Didn't want to bloat the switch statement, so I just separated it into functions
export function handleWindowArgs(args: Array<string>): string { function handleWindowArgs(args: Array<string>): string {
const windows = Windows.getDefault().getWindows(); const specifiedWindow: (Gtk.Window|undefined) = Windows.getWindow(args[1]);
const window = windows[args[1] as never];
if(args[1] == undefined || args[1] == "") if(!specifiedWindow)
return "Window argument not specified!"; return "Window argument not specified!";
if(!Object.hasOwn(windows, args[1]!)) if(!Windows.getList().has(args[1]))
return `Window "${args[1]}" not found windows list!` return `Name "${args[1]}" not found windows map! Make sure to add new Windows on the Map!`
switch(args[0]) { switch(args[0]) {
case "open": case "open":
if(!Windows.getDefault().isVisible(window)) { if(!Windows.isVisible(specifiedWindow)) {
Windows.getDefault().open(window); Windows.open(specifiedWindow);
return `Setting visibility of window "${args[1]}" to true`; return `Setting visibility of window "${args[1]}" to true`;
} }
return `Window is already open, ignored`; return `Window is already open, ignored`;
case "close": case "close":
if(Windows.getDefault().isVisible(window)) { if(Windows.isVisible(specifiedWindow)) {
Windows.getDefault().close(window); Windows.close(specifiedWindow);
return `Setting visibility of window "${args[1]}" to false` return `Setting visibility of window "${args[1]}" to false`
} }
return `Window is already closed, ignored` return `Window is already closed, ignored`
case "toggle": case "toggle":
if(!Windows.getDefault().isVisible(window)) { if(!Windows.isVisible(specifiedWindow)) {
Windows.getDefault().open(window); Windows.open(specifiedWindow);
return `Toggle opening window "${args[1]}"`; return `Toggle opening window "${args[1]}"`;
} }
Windows.getDefault().close(window); Windows.close(specifiedWindow);
return `Toggle closing window "${args[1]}"` return `Toggle closing window "${args[1]}"`
} }
return "Couldn't handle window management arguments" return "Couldn't handle window management arguments"
} }
export function getHelp(): string { function handleVolumeArgs(args: Array<string>) {
return `Manage Astal Windows and do more stuff. From if(!args[1])
return `Please specify what you want to do!\n\n${volumeHelp()}`
if(!/(sink|source)\-mute/.test(args[1]) && !args[2])
return `You forgot to add a value to be set!`;
const command: Array<string> = args[1].split('-');
switch(command[1]) {
case "set":
return `Done! Set ${command[0]} volume to ${args[2]}`;
}
return `Couldn't resolve arguments! "${args.join(' ').replace(new RegExp(`^${args[0]}`), "")}"`;
function volumeHelp(): string {
return `
Control speaker and microphone volumes easily!
Options:
sink-set [number]: set sink(speaker) volume with [number], 0 to ${Wireplumber.getDefault().getMaxSinkVolume()}.
sink-mute: toggle mute for the sink(speaker) device.
sink-increase [number]: increases sink(speaker) volume with [number].
sink-decrease [number]: decreases sink(speaker) volume with [number].
source-set [number]: set source(microphone) volume with [number], 0 to ${Wireplumber.getDefault().getMaxSourceVolume()}.
source-mute: toggle mute for the source(microphone) device.
source-increase [number]: increases source(microphone) volume with [number].
source-decrease [number]: decreases source(microphone) volume with [number]
`.trim();
}
}
function getHelp(): string {
return `
Manage Astal Windows and do more stuff. From
retrozinndev's Hyprland Dots, using Astal and AGS by Aylur. retrozinndev's Hyprland Dots, using Astal and AGS by Aylur.
Options: Options:
@@ -75,5 +112,6 @@ Options:
help, -h, --help: shows this help message. help, -h, --help: shows this help message.
2024 (c) retrozinndev's Hyprland-Dots, licensed under the MIT License. 2024 (c) retrozinndev's Hyprland-Dots, licensed under the MIT License.
https://github.com/retrozinndev/Hyprland-Dots` https://github.com/retrozinndev/Hyprland-Dots
`.trim();
} }
+1 -5
View File
@@ -1,5 +1,4 @@
import AstalNotifd from "gi://AstalNotifd"; import AstalNotifd from "gi://AstalNotifd";
import { Windows } from "./windows";
import { timeout } from "astal/time"; import { timeout } from "astal/time";
const notifd: AstalNotifd.Notifd = new AstalNotifd.Notifd({ const notifd: AstalNotifd.Notifd = new AstalNotifd.Notifd({
@@ -7,13 +6,10 @@ const notifd: AstalNotifd.Notifd = new AstalNotifd.Notifd({
dontDisturb: false dontDisturb: false
}); });
const windows = Windows.getDefault(); export let notifications: Array<AstalNotifd.Notification> = getNotifd().notifications;
export let notifications: Array<AstalNotifd.Notification> = [];
export let notificationHistory: Array<AstalNotifd.Notification> = []; export let notificationHistory: Array<AstalNotifd.Notification> = [];
notifd.connect("notified", (_source: AstalNotifd.Notifd, id: number, _replaced: boolean) => { 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)); addNotification(getNotifd().get_notification(id));
}); });
+3 -3
View File
@@ -1,6 +1,6 @@
import { monitorFile, Process } from "astal"; import { monitorFile, Process } from "astal";
import { astalInstanceName } from "../app";
import { getUserDirs } from "./user"; import { getUserDirs } from "./user";
import { App } from "astal/gtk3";
const monitoringPaths = [ "./scripts", "./window", "./app.ts", "env.d.ts" ]; const monitoringPaths = [ "./scripts", "./window", "./app.ts", "env.d.ts" ];
@@ -12,7 +12,7 @@ export interface InstanceProps {
export function restartInstance(props: InstanceProps = { instanceName: "astal", log: false }): void { export function restartInstance(props: InstanceProps = { instanceName: "astal", log: false }): void {
Process.exec_async(`astal -q ${props.instanceName}`, () => {}); Process.exec_async(`astal -q ${props.instanceName}`, () => {});
Process.exec_async(`ags run ${ props.log && `--log-file Process.exec_async(`ags run ${ props.log && `--log-file
${ getUserDirs().cache}/ags-${ astalInstanceName || "astal" }.log` }`.replaceAll('\n', ' ').trim(), ${ getUserDirs().cache}/ags-${ App.instanceName || "astal" }.log` }`.replaceAll('\n', ' ').trim(),
() => {} () => {}
) )
} }
@@ -22,7 +22,7 @@ export function monitorPaths(): void {
monitorFile( monitorFile(
path, path,
() => restartInstance({ () => restartInstance({
instanceName: astalInstanceName || "astal", instanceName: App.instanceName || "astal",
log: true log: true
}) })
) )
+34 -12
View File
@@ -1,23 +1,45 @@
import { GObject } from "astal";
import AstalWp from "gi://AstalWp"; import AstalWp from "gi://AstalWp";
export class Wireplumber { export const Wireplumber = GObject.registerClass({
private astalWireplumber: (AstalWp.Wp|null) = AstalWp.get_default(); GTypeName: "Wireplumber",
private defaultSink: AstalWp.Endpoint = this.astalWireplumber!.get_default_speaker()!; Signals: {}
private defaultSource: AstalWp.Endpoint = this.astalWireplumber!.get_default_microphone()!; }, class WireplumberClass extends GObject.Object {
private static inst: Wireplumber = new Wireplumber(); private static astalWireplumber: (AstalWp.Wp|null) = AstalWp.get_default();
private static inst: WireplumberClass;
private defaultSink: AstalWp.Endpoint = WireplumberClass.astalWireplumber!.get_default_speaker()!;
private defaultSource: AstalWp.Endpoint = WireplumberClass.astalWireplumber!.get_default_microphone()!;
private maxSinkVolume: number = 100; private maxSinkVolume: number = 100;
private maxSourceVolume: number = 100; private maxSourceVolume: number = 100;
constructor() { _init(...props: any[]) {
if(!this.astalWireplumber) super._init(props);
if(!WireplumberClass.astalWireplumber)
throw new Error("Audio features will not work correctly! Please install wireplumber first", { throw new Error("Audio features will not work correctly! Please install wireplumber first", {
cause: "Wireplumber library not found" cause: "Wireplumber library not found"
}); });
} }
public static getDefault(): Wireplumber { public static getDefault(): WireplumberClass {
return Wireplumber.inst; if(!WireplumberClass.inst)
WireplumberClass.inst = new WireplumberClass();
return WireplumberClass.inst;
}
public static getWireplumber(): AstalWp.Wp {
return WireplumberClass.astalWireplumber!;
}
public getMaxSinkVolume(): number {
return this.maxSinkVolume;
}
public getMaxSourceVolume(): number {
return this.maxSourceVolume;
} }
public getDefaultSink(): AstalWp.Endpoint { public getDefaultSink(): AstalWp.Endpoint {
@@ -29,11 +51,11 @@ export class Wireplumber {
} }
public getSinkVolume(): number { public getSinkVolume(): number {
return this.getDefaultSink().get_volume() * 100; return Math.floor(this.getDefaultSink().get_volume() * 100);
} }
public getSourceVolume(): number { public getSourceVolume(): number {
return this.getDefaultSource().get_volume() * 100; return Math.floor(this.getDefaultSource().get_volume() * 100);
} }
public setSinkVolume(newSinkVolume: number): void { public setSinkVolume(newSinkVolume: number): void {
@@ -123,4 +145,4 @@ export class Wireplumber {
return this.muteSource(); return this.muteSource();
} }
} });
-34
View File
@@ -1,34 +0,0 @@
// get open windows / interact with windows(e.g.: close, open or toggle)
import { Widget } from "astal/gtk3";
import { getWindowsMap } from "../app";
export class Windows {
private static inst: Windows = new Windows();
private readonly windows = getWindowsMap();
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();
}
public toggle(window: Widget.Window): void {
window.is_visible() ? this.close(window) : this.open(window);
}
}
+3 -2
View File
@@ -7,6 +7,7 @@
* { * {
all: unset; all: unset;
transition: 120ms linear; transition: 120ms linear;
color: color.adjust($color: wal.$foreground, $lightness: 15%);
} }
.button-row { .button-row {
@@ -36,12 +37,12 @@ menu {
background: wal.$background; background: wal.$background;
border-radius: 14px; border-radius: 14px;
& > separator { & separator {
margin: 0 4px; margin: 0 4px;
color: wal.$background; color: wal.$background;
} }
& > menuitem { & menuitem {
padding: 8px 0px; padding: 8px 0px;
border-radius: 10px; border-radius: 10px;
font-size: 12px; font-size: 12px;
+18 -12
View File
@@ -1,3 +1,4 @@
@use "sass:color";
@use "./wal"; @use "./wal";
@use "./mixins"; @use "./mixins";
@@ -5,7 +6,7 @@
padding: 6px; padding: 6px;
padding-bottom: 0px; padding-bottom: 0px;
& * { & {
button { button {
padding: 6px 8px; padding: 6px 8px;
border-radius: 12px; border-radius: 12px;
@@ -73,9 +74,9 @@
& > .text-content { & > .text-content {
& > .class { & > .class {
font-size: 9px; font-size: 9px;
font-weight: 500;
font-family: monospace; font-family: monospace;
color: adjust-hue($color: wal.$foreground, $degrees: 100deg); font-weight: 600;
color: color.adjust($color: wal.$foreground, $lightness: -11%);
margin-top: 1px; margin-top: 1px;
} }
@@ -100,7 +101,7 @@
& > .media > box { & > .media > box {
border-radius: 12px; border-radius: 12px;
background: wal.$color1; background: wal.$color1;
padding: 0 6px; padding: 0 7px;
& .icon { & .icon {
margin-right: 6px; margin-right: 6px;
@@ -110,19 +111,25 @@
&.reveal { &.reveal {
& .media > box { & .media > box {
transition: 50ms linear;
border-top-right-radius: 0; border-top-right-radius: 0;
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
} }
& .media-controls { & .media-controls {
padding-left: 3px; padding-left: 6px;
border-top-right-radius: 12px; border-top-right-radius: 12px;
border-bottom-right-radius: 12px; border-bottom-right-radius: 12px;
background: scale-color($color: wal.$color1, $lightness: -20%); transition: unset;
background: linear-gradient(to left, color.adjust($color: wal.$color1, $lightness: -15%) 45px, wal.$color1);
& > button { & > button {
margin: 0px 1px; margin: 0px 1px;
border-radius: 4px; border-radius: 4px;
&:hover {
background: wal.$color2;
}
&:first-child { &:first-child {
border-top-left-radius: 12px; border-top-left-radius: 12px;
border-bottom-left-radius: 12px; border-bottom-left-radius: 12px;
@@ -167,6 +174,11 @@
background: wal.$color1; background: wal.$color1;
} }
& .notification-bell {
padding-left: 10px;
padding-right: 4px;
}
& > box { & > box {
padding: 0 9px; padding: 0 9px;
border-radius: 12px; border-radius: 12px;
@@ -180,10 +192,4 @@
margin-right: 4px; margin-right: 4px;
} }
} }
.cc-toggle button {
$padding-inline: 12px;
padding-left: $padding-inline;
padding-right: calc($padding-inline + 2px);
}
} }
+47 -5
View File
@@ -4,15 +4,20 @@
.control-center-container { .control-center-container {
background: rgba(wal.$background, .65); background: rgba(wal.$background, .65);
border-radius: 24px; border-radius: 24px;
padding: 24px 22px; padding: 20px 14px;
& { & > box {
& button { margin: 9px;
padding: 4px 6px;
&:first-child {
margin-top: 0;
}
&:last-child {
margin-bottom: 0;
}
} }
& .quickactions { & .quickactions {
background: wal.$color1;
& .hostname { & .hostname {
font-size: 15px; font-size: 15px;
font-weight: 600; font-weight: 600;
@@ -21,6 +26,43 @@
& .uptime { & .uptime {
font-size: 12px; font-size: 12px;
} }
& .button-row {
& button {
padding: 4px 6px;
}
}
}
& .sliders {
padding: 2px 6px;
& > box {
margin: 8px 0;
&:first-child {
margin-top: 0;
}
&:last-child {
margin-bottom: 0;
}
}
icon {
background-size: 48px;
}
trough {
background: color.adjust($color: wal.$color1, $lightness: -20%);
min-height: .8em;
border-radius: 8px;
}
trough highlight {
background: wal.$color1;
min-height: inherit;
border-radius: inherit;
} }
} }
} }
+8
View File
@@ -0,0 +1,8 @@
@use "sass:color";
@use "./wal";
@mixin reset-props {
all: unset;
transition: 120ms linear;
color: color.adjust($color: wal.$foreground, $lightness: -15%);
}
+16 -8
View File
@@ -1,11 +1,8 @@
@use "sass:color";
@use "./wal"; @use "./wal";
.osd-window {
all: unset;
.osd { .osd {
margin-bottom: 100px; background: color.change($color: wal.$background, $alpha: 65%);
background: rgba($color: wal.$background, $alpha: .5);
padding: 14px 16px; padding: 14px 16px;
border-radius: 20px; border-radius: 20px;
@@ -17,14 +14,20 @@
.volume { .volume {
margin-top: -6px; margin-top: -6px;
.value { .device {
margin-bottom: 5px; margin-bottom: 5px;
font-size: 14px;
font-weight: 600;
} }
levelbar { levelbar {
trough block { trough block {
border-radius: 6px; border-radius: 2px;
background: wal.$background; background: color.adjust($color: wal.$color1, $lightness: -36%);
&.empty {
border-radius: 2px;
}
&.filled { &.filled {
padding: 3px 0; padding: 3px 0;
@@ -32,6 +35,11 @@
} }
} }
} }
.value {
font-size: 11px;
font-weight: 400;
padding: 0 4px;
} }
} }
} }
+12 -6
View File
@@ -1,6 +1,6 @@
import { bind, Process } from "astal"; import { bind, Process } from "astal";
import { Widget } from "astal/gtk3"; import { Widget } from "astal/gtk3";
import AstalWp from "gi://AstalWp?version=0.1"; import AstalWp from "gi://AstalWp";
import { Wireplumber } from "../../scripts/volume"; import { Wireplumber } from "../../scripts/volume";
const wp = AstalWp.get_default(); const wp = AstalWp.get_default();
@@ -26,8 +26,8 @@ export function Audio() {
} as Widget.LabelProps), } as Widget.LabelProps),
new Widget.Label({ new Widget.Label({
className: "icon nf", className: "icon nf",
label: bind(wp!.defaultSpeaker, "volume").as((volume: number) => label: bind(Wireplumber.getDefault().getDefaultSink(), "volume").as((volume: number) =>
Math.round(volume * 100).toString() + "%") Math.floor(volume * 100) + "%")
} as Widget.LabelProps) } as Widget.LabelProps)
] ]
}) })
@@ -46,12 +46,18 @@ export function Audio() {
label: "󰍬" label: "󰍬"
} as Widget.LabelProps), } as Widget.LabelProps),
new Widget.Label({ new Widget.Label({
label: bind(wp!.defaultMicrophone, "volume").as((volume: number) => label: bind(Wireplumber.getDefault().getDefaultSource(), "volume").as((volume: number) =>
Math.round(volume * 100).toString() + "%") Math.floor(volume * 100) + "%")
} as Widget.LabelProps) } as Widget.LabelProps)
] ]
}) })
} as Widget.EventBoxProps) } as Widget.EventBoxProps),
new Widget.Box({
className: "notification-bell",
child: new Widget.Label({
label: "󰂚"
} as Widget.LabelProps)
} as Widget.BoxProps)
] ]
} as Widget.BoxProps) } as Widget.BoxProps)
} as Widget.EventBoxProps); } as Widget.EventBoxProps);
+3
View File
@@ -1,11 +1,14 @@
import { Widget } from "astal/gtk3"; import { Widget } from "astal/gtk3";
import { getDateTime } from "../../scripts/time"; import { getDateTime } from "../../scripts/time";
import { GLib } from "astal"; import { GLib } from "astal";
import { Windows } from "../../windows";
import { CenterWindow } from "../../window/CenterWindow";
export function Clock(): JSX.Element { export function Clock(): JSX.Element {
return new Widget.Box({ return new Widget.Box({
className: "clock", className: "clock",
child: new Widget.Button({ child: new Widget.Button({
onClick: () => Windows.toggle(CenterWindow),
label: getDateTime().as((dateTime: GLib.DateTime) => { label: getDateTime().as((dateTime: GLib.DateTime) => {
return dateTime.format("%A %d, %H:%M") return dateTime.format("%A %d, %H:%M")
}) })
@@ -1,13 +1,13 @@
import { Widget } from "astal/gtk3"; import { Widget } from "astal/gtk3";
import { Box, Button } from "astal/gtk3/widget";
import AstalHyprland from "gi://AstalHyprland"; import AstalHyprland from "gi://AstalHyprland";
import { tr } from "../../i18n/intl";
export function Logo() { export function Logo() {
return new Widget.Box({ return new Widget.Box({
className: "logo", className: "logo",
//tooltipText: tr("bar.logo.tooltip"), //tooltipText: tr("bar.logo.tooltip"),
child: child: new Widget.Button({
<Button onClick={ () => AstalHyprland.get_default().dispatch("exec", "anyrun") } label={""} /> onClick: () => AstalHyprland.get_default().dispatch("exec", "anyrun"),
label: ""
} as Widget.ButtonProps)
} as Widget.BoxProps); } as Widget.BoxProps);
} }
+10 -3
View File
@@ -1,4 +1,4 @@
import { bind } from "astal"; import { bind, Process } from "astal";
import { Gtk, Widget } from "astal/gtk3"; import { Gtk, Widget } from "astal/gtk3";
import AstalMpris from "gi://AstalMpris"; import AstalMpris from "gi://AstalMpris";
import { Separator, SeparatorProps } from "../Separator"; import { Separator, SeparatorProps } from "../Separator";
@@ -13,7 +13,7 @@ const playerIcons = {
firefox: '󰈹' firefox: '󰈹'
} }
export function Media(): JSX.Element { export function Media(): Gtk.Widget {
const mediaControlsRevealer: Widget.Revealer = new Widget.Revealer({ const mediaControlsRevealer: Widget.Revealer = new Widget.Revealer({
transitionType: Gtk.RevealerTransitionType.SLIDE_RIGHT, transitionType: Gtk.RevealerTransitionType.SLIDE_RIGHT,
transitionDuration: 260, transitionDuration: 260,
@@ -24,6 +24,13 @@ export function Media(): JSX.Element {
homogeneous: false, homogeneous: false,
children: bind(mpris, "players").as((players: Array<AstalMpris.Player>) => children: bind(mpris, "players").as((players: Array<AstalMpris.Player>) =>
players[0] ? [ players[0] ? [
new Widget.Button({
className: "link",
label: "󰌷",
visible: bind(players[0], "metadata").as(metadata =>
metadata?.["xesam:url"] ? true : false),
onClick: () => Process.exec(`echo ${players[0].metadata.url}"`)
} as Widget.ButtonProps),
new Widget.Button({ new Widget.Button({
className: "previous", className: "previous",
label: "󰒮", label: "󰒮",
@@ -82,7 +89,7 @@ export function Media(): JSX.Element {
label: bind(players[0], "artist").as((artist: string) => artist || "No Artist") label: bind(players[0], "artist").as((artist: string) => artist || "No Artist")
} as Widget.LabelProps) } as Widget.LabelProps)
] : new Widget.Label({ ] : new Widget.Label({
label: "Crazy to think this widget didn't disappear yet!" label: "Crazy to think this widget haven't disappeared yet!"
} as Widget.LabelProps) } as Widget.LabelProps)
) )
} as Widget.BoxProps), } as Widget.BoxProps),
+2 -1
View File
@@ -62,6 +62,7 @@ export const QuickActions: Widget.Box = new Widget.Box({
orientation: Gtk.Orientation.VERTICAL, orientation: Gtk.Orientation.VERTICAL,
halign: Gtk.Align.START, halign: Gtk.Align.START,
hexpand: true, hexpand: true,
className: "left",
children: [ children: [
new Widget.Label({ new Widget.Label({
className: "hostname", className: "hostname",
@@ -77,7 +78,7 @@ export const QuickActions: Widget.Box = new Widget.Box({
} as Widget.BoxProps), } as Widget.BoxProps),
new Widget.Box({ new Widget.Box({
orientation: Gtk.Orientation.HORIZONTAL, orientation: Gtk.Orientation.HORIZONTAL,
className: "button-row", className: "right button-row",
halign: Gtk.Align.END, halign: Gtk.Align.END,
hexpand: true, hexpand: true,
children: [ children: [
+54
View File
@@ -0,0 +1,54 @@
import { bind } from "astal";
import { Gtk, Widget } from "astal/gtk3";
import { Wireplumber } from "../../scripts/volume";
export const Sliders: Gtk.Widget = new Widget.Box({
className: "sliders",
orientation: Gtk.Orientation.VERTICAL,
expand: true,
children: [
new Widget.Box({
className: "sink speaker",
children: [
new Widget.Icon({
icon: "audio-volume-high-symbolic"
} as Widget.IconProps),
new Widget.Slider({
drawValue: false,
hexpand: true,
value: bind(Wireplumber.getDefault().getDefaultSink(), "volume").as((volume: number) =>
Math.floor(volume * 100)),
max: Wireplumber.getDefault().getMaxSinkVolume(),
onDragged: (slider: Gtk.Scale) => Wireplumber.getDefault().setSinkVolume(slider.get_value())
} as Widget.SliderProps)
]
} as Widget.BoxProps),
new Widget.Box({
className: "source microphone",
children: [
new Widget.Icon({
icon: "microphone-sensitivity-high-symbolic"
} as Widget.IconProps),
new Widget.Slider({
drawValue: false,
hexpand: true,
value: bind(Wireplumber.getDefault().getDefaultSource(), "volume").as((volume: number) =>
Math.floor(volume * 100)),
max: Wireplumber.getDefault().getMaxSourceVolume(),
onDragged: (slider: Gtk.Scale) => Wireplumber.getDefault().setSourceVolume(slider.get_value())
} as Widget.SliderProps)
]
} as Widget.BoxProps),
/*new Widget.Box({
className: "brightness screen",
children: [
new Widget.Slider({
drawValue: false,
hexpand: true,
value: 216,
max: 255
} as Widget.SliderProps)
]
} as Widget.BoxProps)*/
]
} as Widget.BoxProps);
-3
View File
@@ -9,7 +9,6 @@ import { FocusedClient } from "../widget/bar/FocusedClient";
import { Media } from "../widget/bar/Media"; import { Media } from "../widget/bar/Media";
export const Bar: Widget.Window = new Widget.Window({ export const Bar: Widget.Window = new Widget.Window({
className: "bar",
monitor: 0, monitor: 0,
namespace: "top-bar", namespace: "top-bar",
anchor: Astal.WindowAnchor.TOP, anchor: Astal.WindowAnchor.TOP,
@@ -18,8 +17,6 @@ export const Bar: Widget.Window = new Widget.Window({
canFocus: false, canFocus: false,
visible: true, visible: true,
widthRequest: Gdk.Screen.get_default()?.get_monitor_geometry(0)?.width, widthRequest: Gdk.Screen.get_default()?.get_monitor_geometry(0)?.width,
hexpand: false,
vexpand: false,
child: new Widget.Box({ child: new Widget.Box({
className: "bar-container", className: "bar-container",
child: new Widget.CenterBox({ child: new Widget.CenterBox({
+58
View File
@@ -0,0 +1,58 @@
import { Astal, Gtk, Widget } from "astal/gtk3";
import { GLib } from "astal";
import { getDateTime } from "../scripts/time";
export const CenterWindow: Widget.Window = new Widget.Window({
className: "center-window",
namespace: "center-window",
canFocus: true,
monitor: 0,
layer: Astal.Layer.OVERLAY,
exclusivity: Astal.Exclusivity.NORMAL,
visible: false,
height_request: 600,
anchor: Astal.WindowAnchor.TOP,
child: new Widget.Box({
className: "center-window-container",
children: [
new Widget.Box({
className: "vertical left",
orientation: Gtk.Orientation.VERTICAL,
width_request: 300,
children: [
new Widget.Box({
className: "top time date",
orientation: Gtk.Orientation.VERTICAL,
children: [
new Widget.Label({
className: "time",
label: getDateTime().as((dateTime: GLib.DateTime) =>
dateTime.format("%H:%M"))
} as Widget.LabelProps),
new Widget.Label({
className: "date",
label: getDateTime().as((dateTime: GLib.DateTime) =>
dateTime.format("%A, %B %d %Y"))
} as Widget.LabelProps)
]
} as Widget.BoxProps)
]
} as Widget.BoxProps),
new Widget.Box({
className: "vertical right",
children: [
new Widget.Box({
className: "calendar-box",
child: new Gtk.Calendar({
show_heading: true,
show_day_names: true,
show_week_numbers: false
} as Gtk.Calendar.ConstructorProps)
} as Widget.BoxProps)
]
} as Widget.BoxProps)
]
} as Widget.BoxProps)
} as Widget.WindowProps);
+4 -2
View File
@@ -1,12 +1,14 @@
import { Astal, Gdk, Gtk, Widget } from "astal/gtk3"; import { Astal, Gtk, Widget } from "astal/gtk3";
import { QuickActions } from "../widget/control-center/QuickActions"; import { QuickActions } from "../widget/control-center/QuickActions";
import { Tiles } from "../widget/control-center/Tiles"; import { Tiles } from "../widget/control-center/Tiles";
import { Sliders } from "../widget/control-center/Sliders";
const widgetsContainer: Widget.Box = new Widget.Box({ const widgetsContainer: Widget.Box = new Widget.Box({
className: "control-center-container", className: "control-center-container",
orientation: Gtk.Orientation.VERTICAL, orientation: Gtk.Orientation.VERTICAL,
} as Widget.BoxProps, } as Widget.BoxProps,
QuickActions, QuickActions,
Sliders,
Tiles); Tiles);
export const ControlCenter: Widget.Window = new Widget.Window({ export const ControlCenter: Widget.Window = new Widget.Window({
@@ -15,10 +17,10 @@ export const ControlCenter: Widget.Window = new Widget.Window({
canFocus: true, canFocus: true,
exclusivity: Astal.Exclusivity.NORMAL, exclusivity: Astal.Exclusivity.NORMAL,
anchor: Astal.WindowAnchor.TOP | Astal.WindowAnchor.RIGHT, anchor: Astal.WindowAnchor.TOP | Astal.WindowAnchor.RIGHT,
layer: Astal.Layer.OVERLAY,
margin_top: 10, margin_top: 10,
margin_right: 10, margin_right: 10,
width_request: 450, width_request: 450,
height_request: 400,
monitor: 0, monitor: 0,
visible: false visible: false
} as Widget.WindowProps, widgetsContainer); } as Widget.WindowProps, widgetsContainer);
+19 -30
View File
@@ -1,24 +1,24 @@
import { Astal, Gtk, Widget } from "astal/gtk3"; import { Astal, Gtk, Widget } from "astal/gtk3";
import { getNotifd, removeNotification } from "../scripts/notification-handler"; import { getNotifd, notifications, removeNotification } from "../scripts/notification-handler";
import { notifications as popupNotifications } from "../scripts/notification-handler";
import AstalNotifd from "gi://AstalNotifd"; import AstalNotifd from "gi://AstalNotifd";
import { bind } from "astal";
export const FloatingNotifications: Widget.Window = FloatingNotificationsWindow(); export const FloatingNotifications: Widget.Window = new Widget.Window({
let gtkNotificationPopups: Array<Widget.Box> = []; className: "floating-notifications",
namespace: "floating-notifications",
function FloatingNotificationsWindow(): Widget.Window { canFocus: false,
anchor: Astal.WindowAnchor.RIGHT,
const notificationsBox = new Widget.Box({ monitor: 0,
layer: Astal.Layer.OVERLAY,
visible: false,
exclusivity: Astal.Exclusivity.NORMAL,
child: new Widget.Box({
className: "notifications", className: "notifications",
orientation: Gtk.Orientation.VERTICAL, orientation: Gtk.Orientation.VERTICAL,
homogeneous: false homogeneous: false,
} as Widget.BoxProps); children: bind(getNotifd(), "notifications").as(() => {
notifications.length > 0 ? notifications.map((notification: AstalNotifd.Notification) =>
getNotifd().connect("notified", () => { new Widget.Box({
for(let i = 0; i < popupNotifications.length; i++) {
const notification: AstalNotifd.Notification = popupNotifications[i];
gtkNotificationPopups[i] = new Widget.Box({
className: "notification", className: "notification",
homogeneous: false, homogeneous: false,
children: [ children: [
@@ -40,19 +40,8 @@ function FloatingNotificationsWindow(): Widget.Window {
] ]
} as Widget.BoxProps) } as Widget.BoxProps)
] ]
} as Widget.BoxProps); } as Widget.BoxProps)
} ) : new Widget.Box({})
}) })
} 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); } as Widget.WindowProps);
}
+48 -16
View File
@@ -1,27 +1,41 @@
import { bind } from "astal"; import { bind, Binding, Variable } from "astal";
import { Astal, Gtk, Widget } from "astal/gtk3"; import { Astal, Gtk, Widget } from "astal/gtk3";
import { Time } from "astal/time"; import { Wireplumber } from "../scripts/volume";
import AstalWp from "gi://AstalWp"; import AstalWp from "gi://AstalWp?version=0.1";
import { Windows } from "../scripts/windows";
export const OSD: Widget.Window = OSDWindow(); export enum OSDModes {
SINK,
BRIGHTNESS
}
function OSDWindow() { let osdMode: Variable<OSDModes> = new Variable<OSDModes>(OSDModes.SINK);
return new Widget.Window({ let osdIcon: Binding<string | undefined> = osdMode().as((mode: OSDModes) => {
className: "osd-window", switch(mode) {
case OSDModes.SINK: return "󰕾";
case OSDModes.BRIGHTNESS: return "󰃠";
default: return "󱧣";
}
});
export function setOSDMode(newMode: OSDModes): void {
osdMode.set(newMode);
}
export const OSD: Widget.Window = new Widget.Window({
namespace: "osd", namespace: "osd",
layer: Astal.Layer.OVERLAY, layer: Astal.Layer.OVERLAY,
anchor: Astal.WindowAnchor.BOTTOM, anchor: Astal.WindowAnchor.BOTTOM,
canFocus: false, canFocus: false,
margin_bottom: 80,
monitor: 0, monitor: 0,
visible: false, visible: false,
focusOnClick: false,
child: new Widget.Box({ child: new Widget.Box({
className: "osd", className: "osd",
children: [ children: [
new Widget.Label({ new Widget.Label({
className: "icon", className: "icon",
label: "󰕾", label: osdIcon
css: ".icon { color: white; }"
} as Widget.LabelProps), } as Widget.LabelProps),
new Widget.Box({ new Widget.Box({
className: "volume", className: "volume",
@@ -29,20 +43,38 @@ function OSDWindow() {
valign: Gtk.Align.CENTER, valign: Gtk.Align.CENTER,
children: [ children: [
new Widget.Label({ new Widget.Label({
className: "value", className: "device",
label: bind(AstalWp.get_default()?.defaultSpeaker!, "volume").as((volume: number) => `${Math.round(volume * 100)}%`), label: bind(Wireplumber.getDefault().getDefaultSink(), "name").as((name: string) =>
name || "Speaker"),
halign: Gtk.Align.CENTER halign: Gtk.Align.CENTER
} as Widget.LabelProps), } as Widget.LabelProps),
new Widget.Box({
vexpand: false,
expand: false,
children: [
new Widget.LevelBar({ new Widget.LevelBar({
className: "levelbar", className: "levelbar",
width_request: 120, width_request: 120,
value: bind(AstalWp.get_default()?.defaultSpeaker!, "volume").as((volume: number) => Math.round(volume * 100)), value: bind(Wireplumber.getDefault().getDefaultSink(), "volume").as((volume: number) =>
maxValue: 100, Math.floor(volume * 100)),
maxValue: bind(Wireplumber.getWireplumber(), "defaultSpeaker").as(() =>
Wireplumber.getDefault().getMaxSinkVolume()),
vexpand: false,
expand: false,
halign: Gtk.Align.CENTER halign: Gtk.Align.CENTER
} as Widget.LevelBarProps) } as Widget.LevelBarProps),
/*new Widget.Label({
className: "value",
label: bind(Wireplumber.getDefault().getDefaultSink(), "volume").as((volume: number) =>
`${Math.floor(volume * 100)}%`),
vexpand: false,
expand: false,
halign: Gtk.Align.CENTER
} as Widget.LabelProps)*/
]
} as Widget.BoxProps)
] ]
} as Widget.BoxProps) } as Widget.BoxProps)
] ]
} as Widget.BoxProps) } as Widget.BoxProps)
} as Widget.WindowProps); } as Widget.WindowProps);
}
+57
View File
@@ -0,0 +1,57 @@
import { Gtk, Widget } from "astal/gtk3";
import { Bar } from "./window/Bar";
import { OSD } from "./window/OSD";
import { ControlCenter } from "./window/ControlCenter";
import { CenterWindow } from "./window/CenterWindow";
import { FloatingNotifications } from "./window/FloatingNotifications";
import { GObject } from "astal";
/**
* get open windows / interact with windows(e.g.: close, open or toggle)
*/
export const Windows = GObject.registerClass({
GTypeName: "Windows"
}, class WindowsClass extends GObject.Object {
private static windowsMap: Map<string, Gtk.Window> = new Map<string, Gtk.Window>();
static {
WindowsClass.windowsMap.set("bar", Bar);
WindowsClass.windowsMap.set("osd", OSD);
WindowsClass.windowsMap.set("control-center", ControlCenter);
WindowsClass.windowsMap.set("center-window", CenterWindow);
WindowsClass.windowsMap.set("floating-notifications", FloatingNotifications);
}
public _init(...args: any[]) {
super._init(args);
}
public static setWindow(name: string, window: Gtk.Window): void {
WindowsClass.windowsMap.set(name, window);
}
public static getWindow(name: string): (Gtk.Window|undefined) {
return WindowsClass.windowsMap.get(name);
}
public static getList(): Map<string, Gtk.Window> {
return WindowsClass.windowsMap;
}
public static open(window: Gtk.Window): void {
!WindowsClass.isVisible(window) && window.show();
}
public static isVisible(window: Gtk.Window): boolean {
return window.get_visible();
}
public static close(window: Gtk.Window): void {
WindowsClass.isVisible(window) && window.hide();
}
public static toggle(window: Gtk.Window): void {
window.is_visible() ? WindowsClass.close(window) : WindowsClass.open(window);
}
});