diff --git a/ags/app.ts b/ags/app.ts index 31e776a..e153390 100644 --- a/ags/app.ts +++ b/ags/app.ts @@ -1,36 +1,48 @@ 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 { runStyleHandler } from "./scripts/style-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"; - -export const astalInstanceName = "astal" +let osdTimer: (Time|undefined); App.start({ - instanceName: astalInstanceName || "astal", + instanceName: "astal", requestHandler(request: string, res: (result: any) => void) { console.log(`[LOG] Arguments received: ${request}`) res(handleArguments(request)); }, 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`); runStyleHandler(); //console.log(`[LOG] Starting to monitor scripts to automatically reload instance`); //monitorPaths(); // Only for debugging purposes(testing new widgets and stuff) + + Wireplumber.getDefault().getDefaultSink().connect("notify::volume", () => + !Windows.isVisible(ControlCenter) && triggerOSD(OSDModes.SINK)); } }); -// Windows list -export function getWindowsMap(): Object { - return { - "bar": Bar, - "osd": OSD, - "control-center": ControlCenter, - //"floating-notifications": FloatingNotifications - }; +function triggerOSD(osdModeParam: OSDModes) { + setOSDMode(osdModeParam); + + Windows.open(OSD); + if(!osdTimer) { + osdTimer = timeout(3000, () => { + Windows.close(OSD); + osdTimer = undefined; + }); + } else { + osdTimer.cancel(); + osdTimer = timeout(3000, () => { + Windows.close(OSD); + osdTimer = undefined; + }); + } } diff --git a/ags/scripts/arg-handler.ts b/ags/scripts/arg-handler.ts index e7370bb..a94223c 100644 --- a/ags/scripts/arg-handler.ts +++ b/ags/scripts/arg-handler.ts @@ -1,5 +1,7 @@ -import { Windows } from "./windows"; +import { Gtk } from "astal/gtk3"; +import { Windows } from "../windows"; import { restartInstance } from "./reload-handler"; +import { Wireplumber } from "./volume"; export function handleArguments(request: string): any { const args: Array = request.split(" "); @@ -13,8 +15,11 @@ export function handleArguments(request: string): any { case "h": return getHelp(); // stop it, get some help + case "volume": + return handleVolumeArgs(args); + case "reload": - restartInstance({ log: true, instanceName: "astal" }); + restartInstance({ log: false, instanceName: "astal" }); return "Reloading instance..." 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 -export function handleWindowArgs(args: Array): string { - const windows = Windows.getDefault().getWindows(); - const window = windows[args[1] as never]; +function handleWindowArgs(args: Array): string { + const specifiedWindow: (Gtk.Window|undefined) = Windows.getWindow(args[1]); - if(args[1] == undefined || args[1] == "") + if(!specifiedWindow) return "Window argument not specified!"; - if(!Object.hasOwn(windows, args[1]!)) - return `Window "${args[1]}" not found windows list!` + if(!Windows.getList().has(args[1])) + return `Name "${args[1]}" not found windows map! Make sure to add new Windows on the Map!` switch(args[0]) { case "open": - if(!Windows.getDefault().isVisible(window)) { - Windows.getDefault().open(window); + if(!Windows.isVisible(specifiedWindow)) { + Windows.open(specifiedWindow); 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); + if(Windows.isVisible(specifiedWindow)) { + Windows.close(specifiedWindow); 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); + if(!Windows.isVisible(specifiedWindow)) { + Windows.open(specifiedWindow); return `Toggle opening window "${args[1]}"`; } - Windows.getDefault().close(window); + Windows.close(specifiedWindow); 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 +function handleVolumeArgs(args: Array) { + 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 = 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. Options: @@ -75,5 +112,6 @@ Options: help, -h, --help: shows this help message. 2024 (c) retrozinndev's Hyprland-Dots, licensed under the MIT License. -https://github.com/retrozinndev/Hyprland-Dots` +https://github.com/retrozinndev/Hyprland-Dots +`.trim(); } diff --git a/ags/scripts/notification-handler.ts b/ags/scripts/notification-handler.ts index 1348495..fdc8a13 100644 --- a/ags/scripts/notification-handler.ts +++ b/ags/scripts/notification-handler.ts @@ -1,5 +1,4 @@ import AstalNotifd from "gi://AstalNotifd"; -import { Windows } from "./windows"; import { timeout } from "astal/time"; const notifd: AstalNotifd.Notifd = new AstalNotifd.Notifd({ @@ -7,13 +6,10 @@ const notifd: AstalNotifd.Notifd = new AstalNotifd.Notifd({ dontDisturb: false }); -const windows = Windows.getDefault(); - -export let notifications: Array = []; +export let notifications: Array = getNotifd().notifications; export let notificationHistory: Array = []; 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)); }); diff --git a/ags/scripts/reload-handler.ts b/ags/scripts/reload-handler.ts index 4f1c13d..088d35b 100644 --- a/ags/scripts/reload-handler.ts +++ b/ags/scripts/reload-handler.ts @@ -1,6 +1,6 @@ import { monitorFile, Process } from "astal"; -import { astalInstanceName } from "../app"; import { getUserDirs } from "./user"; +import { App } from "astal/gtk3"; 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 { 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(), + ${ getUserDirs().cache}/ags-${ App.instanceName || "astal" }.log` }`.replaceAll('\n', ' ').trim(), () => {} ) } @@ -22,7 +22,7 @@ export function monitorPaths(): void { monitorFile( path, () => restartInstance({ - instanceName: astalInstanceName || "astal", + instanceName: App.instanceName || "astal", log: true }) ) diff --git a/ags/scripts/volume.ts b/ags/scripts/volume.ts index a4764d6..2066c94 100644 --- a/ags/scripts/volume.ts +++ b/ags/scripts/volume.ts @@ -1,23 +1,45 @@ +import { GObject } from "astal"; 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(); +export const Wireplumber = GObject.registerClass({ + GTypeName: "Wireplumber", + Signals: {} +}, class WireplumberClass extends GObject.Object { + 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 maxSourceVolume: number = 100; - constructor() { - if(!this.astalWireplumber) + _init(...props: any[]) { + super._init(props); + + if(!WireplumberClass.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 static getDefault(): WireplumberClass { + 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 { @@ -29,11 +51,11 @@ export class Wireplumber { } public getSinkVolume(): number { - return this.getDefaultSink().get_volume() * 100; + return Math.floor(this.getDefaultSink().get_volume() * 100); } public getSourceVolume(): number { - return this.getDefaultSource().get_volume() * 100; + return Math.floor(this.getDefaultSource().get_volume() * 100); } public setSinkVolume(newSinkVolume: number): void { @@ -123,4 +145,4 @@ export class Wireplumber { return this.muteSource(); } -} +}); diff --git a/ags/scripts/windows.ts b/ags/scripts/windows.ts deleted file mode 100644 index e28f098..0000000 --- a/ags/scripts/windows.ts +++ /dev/null @@ -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); - } -} diff --git a/ags/style.scss b/ags/style.scss index a05859b..2325934 100644 --- a/ags/style.scss +++ b/ags/style.scss @@ -7,6 +7,7 @@ * { all: unset; transition: 120ms linear; + color: color.adjust($color: wal.$foreground, $lightness: 15%); } .button-row { @@ -36,12 +37,12 @@ menu { background: wal.$background; border-radius: 14px; - & > separator { + & separator { margin: 0 4px; color: wal.$background; } - & > menuitem { + & menuitem { padding: 8px 0px; border-radius: 10px; font-size: 12px; diff --git a/ags/style/_bar.scss b/ags/style/_bar.scss index c96fca1..161989a 100644 --- a/ags/style/_bar.scss +++ b/ags/style/_bar.scss @@ -1,3 +1,4 @@ +@use "sass:color"; @use "./wal"; @use "./mixins"; @@ -5,7 +6,7 @@ padding: 6px; padding-bottom: 0px; - & * { + & { button { padding: 6px 8px; border-radius: 12px; @@ -73,9 +74,9 @@ & > .text-content { & > .class { font-size: 9px; - font-weight: 500; 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; } @@ -100,7 +101,7 @@ & > .media > box { border-radius: 12px; background: wal.$color1; - padding: 0 6px; + padding: 0 7px; & .icon { margin-right: 6px; @@ -110,19 +111,25 @@ &.reveal { & .media > box { + transition: 50ms linear; border-top-right-radius: 0; border-bottom-right-radius: 0; } & .media-controls { - padding-left: 3px; + padding-left: 6px; border-top-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 { margin: 0px 1px; border-radius: 4px; + &:hover { + background: wal.$color2; + } + &:first-child { border-top-left-radius: 12px; border-bottom-left-radius: 12px; @@ -167,6 +174,11 @@ background: wal.$color1; } + & .notification-bell { + padding-left: 10px; + padding-right: 4px; + } + & > box { padding: 0 9px; border-radius: 12px; @@ -180,10 +192,4 @@ margin-right: 4px; } } - - .cc-toggle button { - $padding-inline: 12px; - padding-left: $padding-inline; - padding-right: calc($padding-inline + 2px); - } } diff --git a/ags/style/_control-center.scss b/ags/style/_control-center.scss index 5cf343e..64df74d 100644 --- a/ags/style/_control-center.scss +++ b/ags/style/_control-center.scss @@ -4,23 +4,65 @@ .control-center-container { background: rgba(wal.$background, .65); border-radius: 24px; - padding: 24px 22px; + padding: 20px 14px; - & { - & button { - padding: 4px 6px; + & > box { + margin: 9px; + + &:first-child { + margin-top: 0; + } + &:last-child { + margin-bottom: 0; + } + } + + & .quickactions { + & .hostname { + font-size: 15px; + font-weight: 600; } - & .quickactions { - background: wal.$color1; - & .hostname { - font-size: 15px; - font-weight: 600; - } + & .uptime { + font-size: 12px; + } - & .uptime { - 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; + } + } } diff --git a/ags/style/_mixins.scss b/ags/style/_mixins.scss index e69de29..adf150d 100644 --- a/ags/style/_mixins.scss +++ b/ags/style/_mixins.scss @@ -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%); +} diff --git a/ags/style/_osd.scss b/ags/style/_osd.scss index 50f61a0..5ded07b 100644 --- a/ags/style/_osd.scss +++ b/ags/style/_osd.scss @@ -1,37 +1,45 @@ +@use "sass:color"; @use "./wal"; -.osd-window { - all: unset; +.osd { + background: color.change($color: wal.$background, $alpha: 65%); + padding: 14px 16px; + border-radius: 20px; - .osd { - margin-bottom: 100px; - background: rgba($color: wal.$background, $alpha: .5); - padding: 14px 16px; - border-radius: 20px; + .icon { + margin-right: 14px; + font-size: 24px; + } - .icon { - margin-right: 14px; - font-size: 24px; + .volume { + margin-top: -6px; + + .device { + margin-bottom: 5px; + font-size: 14px; + font-weight: 600; } - .volume { - margin-top: -6px; + levelbar { + trough block { + border-radius: 2px; + background: color.adjust($color: wal.$color1, $lightness: -36%); - .value { - margin-bottom: 5px; - } + &.empty { + border-radius: 2px; + } - levelbar { - trough block { - border-radius: 6px; - background: wal.$background; - - &.filled { - padding: 3px 0; - background: wal.$color1; - } + &.filled { + padding: 3px 0; + background: wal.$color1; } } } + + .value { + font-size: 11px; + font-weight: 400; + padding: 0 4px; + } } } diff --git a/ags/widget/Separator.tsx b/ags/widget/Separator.ts similarity index 100% rename from ags/widget/Separator.tsx rename to ags/widget/Separator.ts diff --git a/ags/widget/bar/Audio.ts b/ags/widget/bar/Audio.ts index b11d3c5..1c55283 100644 --- a/ags/widget/bar/Audio.ts +++ b/ags/widget/bar/Audio.ts @@ -1,6 +1,6 @@ import { bind, Process } from "astal"; import { Widget } from "astal/gtk3"; -import AstalWp from "gi://AstalWp?version=0.1"; +import AstalWp from "gi://AstalWp"; import { Wireplumber } from "../../scripts/volume"; const wp = AstalWp.get_default(); @@ -26,8 +26,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(Wireplumber.getDefault().getDefaultSink(), "volume").as((volume: number) => + Math.floor(volume * 100) + "%") } as Widget.LabelProps) ] }) @@ -46,12 +46,18 @@ 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(Wireplumber.getDefault().getDefaultSource(), "volume").as((volume: number) => + Math.floor(volume * 100) + "%") } 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.EventBoxProps); diff --git a/ags/widget/bar/Clock.ts b/ags/widget/bar/Clock.ts index 18ae79d..fdd44a0 100644 --- a/ags/widget/bar/Clock.ts +++ b/ags/widget/bar/Clock.ts @@ -1,11 +1,14 @@ import { Widget } from "astal/gtk3"; import { getDateTime } from "../../scripts/time"; import { GLib } from "astal"; +import { Windows } from "../../windows"; +import { CenterWindow } from "../../window/CenterWindow"; export function Clock(): JSX.Element { return new Widget.Box({ className: "clock", child: new Widget.Button({ + onClick: () => Windows.toggle(CenterWindow), label: getDateTime().as((dateTime: GLib.DateTime) => { return dateTime.format("%A %d, %H:%M") }) diff --git a/ags/widget/bar/Logo.tsx b/ags/widget/bar/Logo.ts similarity index 53% rename from ags/widget/bar/Logo.tsx rename to ags/widget/bar/Logo.ts index 2c0c5c8..94ef404 100644 --- a/ags/widget/bar/Logo.tsx +++ b/ags/widget/bar/Logo.ts @@ -1,13 +1,13 @@ import { Widget } from "astal/gtk3"; -import { Box, Button } from "astal/gtk3/widget"; import AstalHyprland from "gi://AstalHyprland"; -import { tr } from "../../i18n/intl"; export function Logo() { return new Widget.Box({ className: "logo", //tooltipText: tr("bar.logo.tooltip"), - child: -