diff options
Diffstat (limited to 'modules/osds.tsx')
| -rw-r--r-- | modules/osds.tsx | 326 |
1 files changed, 0 insertions, 326 deletions
diff --git a/modules/osds.tsx b/modules/osds.tsx deleted file mode 100644 index b6e1333..0000000 --- a/modules/osds.tsx +++ /dev/null @@ -1,326 +0,0 @@ -import { execAsync, register, timeout, Variable, type Time } from "astal"; -import { App, Astal, Gtk, Widget } from "astal/gtk3"; -import cairo from "cairo"; -import AstalWp from "gi://AstalWp"; -import Cairo from "gi://cairo"; -import Pango from "gi://Pango"; -import PangoCairo from "gi://PangoCairo"; -import { osds as config } from "../config"; -import Monitors, { type Monitor } from "../services/monitors"; -import { PopupWindow } from "../utils/widgets"; - -const getStyle = (context: Gtk.StyleContext, prop: string) => context.get_property(prop, Gtk.StateFlags.NORMAL); -const getNumStyle = (context: Gtk.StyleContext, prop: string) => getStyle(context, prop) as number; - -const mix = (a: number, b: number, r: number) => a * r + b * (1 - r); - -const pangoWeightToStr = (weight: Pango.Weight) => { - switch (weight) { - case Pango.Weight.ULTRALIGHT: - return "UltraLight"; - case Pango.Weight.LIGHT: - return "Light"; - case Pango.Weight.BOLD: - return "Bold"; - case Pango.Weight.ULTRABOLD: - return "UltraBold"; - case Pango.Weight.HEAVY: - return "Heavy"; - default: - return "Normal"; - } -}; - -const SliderOsd = ({ - fillIcons, - monitor, - type, - windowSetup, - className = "", - initValue, - drawAreaSetup, -}: { - fillIcons?: boolean; - monitor?: Monitor; - type: "volume" | "brightness"; - windowSetup: (self: Widget.Window, show: () => void) => void; - className?: string; - initValue: number; - drawAreaSetup: (self: Widget.DrawingArea, icon: Variable<string>) => void; -}) => ( - <PopupWindow - name={type} - monitor={monitor?.id} - keymode={Astal.Keymode.NONE} - anchor={config[type].position} - margin={config[type].margin} - setup={self => { - let time: Time | null = null; - const hideAfterTimeout = () => { - time?.cancel(); - time = timeout(config[type].hideDelay, () => self.hide()); - }; - self.connect("show", hideAfterTimeout); - windowSetup(self, () => { - self.show(); - hideAfterTimeout(); - }); - }} - > - <box className={type}> - <drawingarea - className={`inner ${className}`} - css={"font-size: " + initValue + "px;"} - setup={self => { - const halfPi = Math.PI / 2; - const vertical = - config[type].position === Astal.WindowAnchor.LEFT || - config[type].position === Astal.WindowAnchor.RIGHT; - - const icon = Variable(""); - drawAreaSetup(self, icon); - self.hook(icon, () => self.queue_draw()); - - // Init size - const styleContext = self.get_style_context(); - const width = getNumStyle(styleContext, "min-width"); - const height = getNumStyle(styleContext, "min-height"); - if (vertical) self.set_size_request(height, width); - else self.set_size_request(width, height); - - let fontDesc: Pango.FontDescription | null = null; - - self.connect("draw", (_, cr: cairo.Context) => { - const parent = self.get_parent(); - if (!parent) return; - - const styleContext = self.get_style_context(); - const pContext = parent.get_style_context(); - - let width = getNumStyle(styleContext, "min-width"); - let height = getNumStyle(styleContext, "min-height"); - - const progressValue = getNumStyle(styleContext, "font-size"); - let radius = getNumStyle(pContext, "border-radius"); - // Flatten when near 0, do before swap cause its simpler - radius = Math.min(radius, Math.min(width * progressValue, height) / 2); - - if (vertical) [width, height] = [height, width]; // Swap if vertical - self.set_size_request(width, height); - - const progressPosition = vertical - ? height * (1 - progressValue) + radius // Top is 0, but we want it to start from the bottom - : width * progressValue - radius; - - const bg = styleContext.get_background_color(Gtk.StateFlags.NORMAL); - cr.setSourceRGBA(bg.red, bg.green, bg.blue, bg.alpha); - - // Background - if (vertical) { - cr.arc(radius, progressPosition, radius, -Math.PI, -halfPi); // Top left - cr.arc(width - radius, progressPosition, radius, -halfPi, 0); // Top right - cr.arc(width - radius, height - radius, radius, 0, halfPi); // Bottom right - } else { - cr.arc(radius, radius, radius, -Math.PI, -halfPi); // Top left - cr.arc(progressPosition, radius, radius, -halfPi, 0); // Top right - cr.arc(progressPosition, height - radius, radius, 0, halfPi); // Bottom right - } - cr.arc(radius, height - radius, radius, halfPi, Math.PI); // Bottom left - cr.fill(); - - const fg = pContext.get_background_color(Gtk.StateFlags.NORMAL); - cr.setAntialias(cairo.Antialias.BEST); - - // Progress number, at top/right - let nw = 0; - let nh = 0; - if (config[type].showValue) { - const numLayout = parent.create_pango_layout(String(Math.round(progressValue * 100))); - [nw, nh] = numLayout.get_pixel_size(); - let diff; - if (vertical) { - diff = ((1 - progressValue) * height) / nh; - cr.moveTo((width - nw) / 2, radius / 2); - } else { - diff = ((1 - progressValue) * width) / nw; - cr.moveTo(width - nw - radius, (height - nh) / 2); - } - diff = Math.max(0, Math.min(1, diff)); - - cr.setSourceRGBA( - mix(bg.red, fg.red, diff), - mix(bg.green, fg.green, diff), - mix(bg.blue, fg.blue, diff), - mix(bg.alpha, fg.alpha, diff) - ); - - PangoCairo.show_layout(cr, numLayout); - } - - // Progress icon, follows progress - if (fontDesc === null) { - const weight = pangoWeightToStr(getStyle(pContext, "font-weight") as Pango.Weight); - const size = getNumStyle(pContext, "font-size") * 1.5; - fontDesc = Pango.font_description_from_string( - `Material Symbols Rounded ${weight} ${size}px` - ); - // Ugh GTK CSS doesn't support font-variations, so you need to manually create the layout and font desc instead of using Gtk.Widget#create_pango_layout - if (fillIcons) fontDesc.set_variations("FILL=1"); - } - - const iconLayout = PangoCairo.create_layout(cr); - iconLayout.set_font_description(fontDesc); - iconLayout.set_text(icon.get(), -1); - - const [iw, ih] = iconLayout.get_pixel_size(); - let diff; - if (vertical) { - diff = (progressValue * height) / ih; - cr.moveTo( - (width - iw) / 2, - Math.max(nh, Math.min(height - ih, progressPosition - ih / 2 + radius)) - ); - } else { - diff = (progressValue * width) / iw; - cr.moveTo( - Math.min( - width - nw * 1.1 - iw - radius, - Math.max(0, progressPosition - iw / 2 - radius) - ), - (height - ih) / 2 - ); - } - diff = Math.max(0, Math.min(1, diff)); - - cr.setSourceRGBA( - mix(fg.red, bg.red, diff), - mix(fg.green, bg.green, diff), - mix(fg.blue, bg.blue, diff), - mix(fg.alpha, bg.alpha, diff) - ); - - PangoCairo.show_layout(cr, iconLayout); - }); - }} - /> - </box> - </PopupWindow> -); - -const Volume = ({ audio }: { audio: AstalWp.Audio }) => ( - <SliderOsd - fillIcons - type="volume" - windowSetup={(self, show) => { - self.hook(audio.defaultSpeaker, "notify::volume", show); - self.hook(audio.defaultSpeaker, "notify::mute", show); - }} - className={audio.defaultSpeaker.mute ? "mute" : ""} - initValue={audio.defaultSpeaker.volume} - drawAreaSetup={(self, icon) => { - const updateIcon = () => { - if (/head(phone|set)/i.test(audio.defaultSpeaker.icon)) icon.set("headphones"); - else if (audio.defaultSpeaker.mute) icon.set("no_sound"); - else if (audio.defaultSpeaker.volume === 0) icon.set("volume_mute"); - else if (audio.defaultSpeaker.volume <= 0.5) icon.set("volume_down"); - else icon.set("volume_up"); - }; - updateIcon(); - self.hook(audio.defaultSpeaker, "notify::icon", updateIcon); - self.hook(audio.defaultSpeaker, "notify::mute", () => { - updateIcon(); - self.toggleClassName("mute", audio.defaultSpeaker.mute); - }); - self.hook(audio.defaultSpeaker, "notify::volume", () => { - updateIcon(); - self.css = `font-size: ${audio.defaultSpeaker.volume}px`; - }); - }} - /> -); - -const Brightness = ({ monitor }: { monitor: Monitor }) => ( - <SliderOsd - monitor={monitor} - type="brightness" - windowSetup={(self, show) => self.hook(monitor, "notify::brightness", show)} - initValue={monitor.brightness} - drawAreaSetup={(self, icon) => { - const update = () => { - if (monitor.brightness > 0.66) icon.set("brightness_high"); - else if (monitor.brightness > 0.33) icon.set("brightness_medium"); - else if (monitor.brightness > 0) icon.set("brightness_low"); - else icon.set("brightness_empty"); - self.css = `font-size: ${monitor.brightness}px`; - }; - self.hook(monitor, "notify::brightness", update); - update(); - }} - /> -); - -@register() -class LockOsd extends Widget.Window { - readonly lockType: "caps" | "num"; - - #timeout: Time | null = null; - - constructor({ type, icon, right }: { type: "caps" | "num"; icon: string; right?: boolean }) { - super({ - visible: false, - name: `lock-${type}`, - application: App, - namespace: `caelestia-lock-${type}`, - anchor: - Astal.WindowAnchor.TOP | Astal.WindowAnchor.LEFT | Astal.WindowAnchor.BOTTOM | Astal.WindowAnchor.RIGHT, - exclusivity: Astal.Exclusivity.IGNORE, - }); - - this.lockType = type; - this.#update(); - - this.add( - <box vertical halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER} className={`lock ${type}`}> - <label vexpand className="icon" label={icon} /> - <label vexpand className="text" label={type.slice(0, 1).toUpperCase() + type.slice(1) + "lock"} /> - </box> - ); - - // Clickthrough - this.connect("size-allocate", () => this.input_shape_combine_region(new Cairo.Region())); - - // Move over when other indicator opens/closes - this.hook(App, "window-toggled", (_, window) => { - if (window !== this && window instanceof LockOsd) { - const child = this.get_child(); - if (!child) return; - this[right ? "marginLeft" : "marginRight"] = window.visible - ? child.get_preferred_width()[1] + config.lock.spacing - : 0; - } - }); - } - - #update() { - execAsync(`fish -c 'cat /sys/class/leds/input*::${this.lockType}lock/brightness'`) - .then(out => (this.get_child() as Widget.Box | null)?.toggleClassName("enabled", out.includes("1"))) - .catch(console.error); - } - - show() { - super.show(); - this.#update(); - this.#timeout?.cancel(); - this.#timeout = timeout(config.lock[this.lockType].hideDelay, () => this.hide()); - } -} - -export default () => { - if (AstalWp.get_default()) <Volume audio={AstalWp.get_default()!.audio} />; - Monitors.get_default().forEach(monitor => <Brightness monitor={monitor} />); - - <LockOsd type="caps" icon="keyboard_capslock" />; - <LockOsd right type="num" icon="filter_1" />; - - return null; -}; |