From 75ab0bfd12eda548b7330af5394648820daab24f Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 23 Nov 2025 14:46:20 +0100 Subject: [PATCH] Switch to noctalia --- flake.lock | 21 ++ flake.nix | 4 + modules/wm/ags/.editorconfig | 14 -- modules/wm/ags/.gitignore | 4 - modules/wm/ags/config.js | 48 ---- modules/wm/ags/covercolors.py | 49 ---- modules/wm/ags/default.nix | 48 ---- modules/wm/ags/layouts/bar.js | 88 ------- modules/wm/ags/layouts/notifications.js | 55 ----- modules/wm/ags/layouts/osd.js | 85 ------- modules/wm/ags/layouts/quicksettings.js | 160 ------------- modules/wm/ags/misc/circular-progress.js | 166 ------------- modules/wm/ags/misc/menu.js | 186 --------------- modules/wm/ags/misc/popup.js | 198 ---------------- modules/wm/ags/misc/utils.js | 32 --- modules/wm/ags/modules/audio.js | 228 ------------------ modules/wm/ags/modules/battery.js | 28 --- modules/wm/ags/modules/bluetooth.js | 94 -------- modules/wm/ags/modules/brightness.js | 38 --- modules/wm/ags/modules/clock.js | 14 -- modules/wm/ags/modules/darkmode.js | 57 ----- modules/wm/ags/modules/mpris.js | 290 ----------------------- modules/wm/ags/modules/network.js | 114 --------- modules/wm/ags/modules/notifications.js | 270 --------------------- modules/wm/ags/modules/powerprofile.js | 62 ----- modules/wm/ags/modules/systray.js | 52 ---- modules/wm/ags/modules/wm.js | 96 -------- modules/wm/ags/services/brightness.js | 44 ---- modules/wm/ags/style.css | 232 ------------------ modules/wm/ags/tsconfig.json | 18 -- modules/wm/ags/types | 1 - modules/wm/home.nix | 126 +++++++++- modules/wm/quickshell/.qmlformat.ini | 1 - modules/wm/quickshell/.qmlls.ini | 1 - modules/wm/quickshell/default.nix | 10 - modules/wm/quickshell/shell.qml | 16 -- modules/wm/quickshell/utils/Settings.qml | 13 - modules/wm/quickshell/widgets/Bar.qml | 37 --- 38 files changed, 150 insertions(+), 2850 deletions(-) delete mode 100644 modules/wm/ags/.editorconfig delete mode 100644 modules/wm/ags/.gitignore delete mode 100644 modules/wm/ags/config.js delete mode 100644 modules/wm/ags/covercolors.py delete mode 100644 modules/wm/ags/default.nix delete mode 100644 modules/wm/ags/layouts/bar.js delete mode 100644 modules/wm/ags/layouts/notifications.js delete mode 100644 modules/wm/ags/layouts/osd.js delete mode 100644 modules/wm/ags/layouts/quicksettings.js delete mode 100644 modules/wm/ags/misc/circular-progress.js delete mode 100644 modules/wm/ags/misc/menu.js delete mode 100644 modules/wm/ags/misc/popup.js delete mode 100644 modules/wm/ags/misc/utils.js delete mode 100644 modules/wm/ags/modules/audio.js delete mode 100644 modules/wm/ags/modules/battery.js delete mode 100644 modules/wm/ags/modules/bluetooth.js delete mode 100644 modules/wm/ags/modules/brightness.js delete mode 100644 modules/wm/ags/modules/clock.js delete mode 100644 modules/wm/ags/modules/darkmode.js delete mode 100644 modules/wm/ags/modules/mpris.js delete mode 100644 modules/wm/ags/modules/network.js delete mode 100644 modules/wm/ags/modules/notifications.js delete mode 100644 modules/wm/ags/modules/powerprofile.js delete mode 100644 modules/wm/ags/modules/systray.js delete mode 100644 modules/wm/ags/modules/wm.js delete mode 100644 modules/wm/ags/services/brightness.js delete mode 100644 modules/wm/ags/style.css delete mode 100644 modules/wm/ags/tsconfig.json delete mode 120000 modules/wm/ags/types delete mode 100644 modules/wm/quickshell/.qmlformat.ini delete mode 120000 modules/wm/quickshell/.qmlls.ini delete mode 100644 modules/wm/quickshell/default.nix delete mode 100644 modules/wm/quickshell/shell.qml delete mode 100644 modules/wm/quickshell/utils/Settings.qml delete mode 100644 modules/wm/quickshell/widgets/Bar.qml diff --git a/flake.lock b/flake.lock index ad260ae..c7b5e38 100644 --- a/flake.lock +++ b/flake.lock @@ -236,6 +236,26 @@ "type": "indirect" } }, + "noctalia": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1763904998, + "narHash": "sha256-MMJtTkftqGLSShrPtObN/ZB9eveq6JQDzrdXvQ8UwGY=", + "owner": "zoriya", + "repo": "noctalia-shell", + "rev": "8975e4f979bf7ef2c7a62446761546899c336dd8", + "type": "github" + }, + "original": { + "owner": "zoriya", + "repo": "noctalia-shell", + "type": "github" + } + }, "root": { "inputs": { "home-manager": "home-manager", @@ -247,6 +267,7 @@ "nixos-hardware": "nixos-hardware", "nixos-wsl": "nixos-wsl", "nixpkgs": "nixpkgs_2", + "noctalia": "noctalia", "tmux": "tmux", "zen-browser": "zen-browser" } diff --git a/flake.nix b/flake.nix index f458ba8..c4382b3 100644 --- a/flake.nix +++ b/flake.nix @@ -28,6 +28,10 @@ url = "github:youwen5/zen-browser-flake"; inputs.nixpkgs.follows = "nixpkgs"; }; + noctalia = { + url = "github:zoriya/noctalia-shell"; + inputs.nixpkgs.follows = "nixpkgs"; + }; # use tmux's master for mode 2031 tmux = { url = "github:tmux/tmux"; diff --git a/modules/wm/ags/.editorconfig b/modules/wm/ags/.editorconfig deleted file mode 100644 index d40f954..0000000 --- a/modules/wm/ags/.editorconfig +++ /dev/null @@ -1,14 +0,0 @@ -root = true - -[*] -charset = utf-8 -end_of_line = lf -trim_trailing_whitespace = true -insert_final_newline = true -indent_style = tab -indent_size = tab -max_line_length = 120 - -[{*.yaml,*.yml}] -indent_style = space -indent_size = 2 diff --git a/modules/wm/ags/.gitignore b/modules/wm/ags/.gitignore deleted file mode 100644 index d0cdead..0000000 --- a/modules/wm/ags/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -node_modules -package-lock.json -weather_key -settings.json diff --git a/modules/wm/ags/config.js b/modules/wm/ags/config.js deleted file mode 100644 index d1f85e5..0000000 --- a/modules/wm/ags/config.js +++ /dev/null @@ -1,48 +0,0 @@ -import Gtk from "gi://Gtk?version=3.0"; -import Gdk from "gi://Gdk"; - -import { Bar } from "./layouts/bar.js"; -import { Notifications } from "./layouts/notifications.js"; -import { OSD } from "./layouts/osd.js"; -import { Quicksettings } from "./layouts/quicksettings.js"; - -/** - * @param {Array<(monitor: number) => Gtk.Window>} widgets - */ -export function forMonitors(widgets) { - const display = Gdk.Display.get_default(); - - display?.connect("monitor-added", (disp, gdkmonitor) => { - let monitor = 0; - for (let i = 0; i < display.get_n_monitors(); i++) { - if (gdkmonitor === display.get_monitor(i)) { - monitor = i; - break; - } - } - - widgets.forEach((win) => App.addWindow(win(monitor))); - }); - - display?.connect("monitor-removed", (disp, monitor) => { - App.windows.forEach((win) => { - // @ts-ignore - if (win.gdkmonitor === monitor) App.removeWindow(win); - }); - }); - - const n = display?.get_n_monitors() || 1; - return Array.from({ length: n }, (_, i) => i).flatMap((mon) => - widgets.map((x) => x(mon)), - ); -} - -App.config({ - closeWindowDelay: { - quicksettings: 300, - notifications: 200, - osd: 300, - }, - style: `${App.configDir}/style.css`, - windows: [...forMonitors([Bar]), Quicksettings(), Notifications(), OSD()], -}); diff --git a/modules/wm/ags/covercolors.py b/modules/wm/ags/covercolors.py deleted file mode 100644 index db3059d..0000000 --- a/modules/wm/ags/covercolors.py +++ /dev/null @@ -1,49 +0,0 @@ -#! /usr/bin/env python3 - -from material_color_utilities_python import * -from PIL import Image -import json -import os -import sys -import hashlib - -CACHE_DIR = os.path.expanduser("~/.cache/ags/tempcolors") - -def cache_and_get_colors(file_path): - file_hash = hashlib.sha256(file_path.encode('utf-8')).hexdigest() - cache_path = os.path.join(CACHE_DIR, file_hash[:2], file_hash[2:]) - - os.makedirs(cache_path, exist_ok=True) - - colors_path = os.path.join(cache_path, "colors.json") - if os.path.exists(colors_path): - with open(colors_path, "r") as f: - parsed_colors = json.load(f) - elif not os.path.exists(file_path): - return None - img = Image.open(file_path) - basewidth = 8 - wpercent = (basewidth/float(img.size[0])) - hsize = int((float(img.size[1])*float(wpercent))) - img = img.resize((basewidth, hsize), Image.Resampling.LANCZOS) - theme = themeFromImage(img) - - scheme = theme.get("schemes") - - parsed_colors = { - "primary": hexFromArgb(scheme.get("dark").primary), - "onPrimary": hexFromArgb(scheme.get("dark").onPrimary), - "background": hexFromArgb(scheme.get("dark").background), - "onBackground": hexFromArgb(scheme.get("dark").onBackground), - } - - with open(colors_path, "w") as f: - json.dump(parsed_colors, f) - - return parsed_colors - -fn = sys.argv[1] - -parsed_colors = cache_and_get_colors(fn) - -print(json.dumps(parsed_colors, indent=4)) diff --git a/modules/wm/ags/default.nix b/modules/wm/ags/default.nix deleted file mode 100644 index 2098f3b..0000000 --- a/modules/wm/ags/default.nix +++ /dev/null @@ -1,48 +0,0 @@ -{pkgs, ...}: let - covercolors = pkgs.stdenv.mkDerivation { - name = "covercolors"; - dontUnpack = true; - propagatedBuildInputs = [ - (pkgs.python3.withPackages (pyPkgs: - with pyPkgs; [ - material-color-utilities - pillow - ])) - ]; - installPhase = "install -Dm755 ${./covercolors.py} $out/bin/covercolors"; - }; - # systemdTarget = "graphical-session.target"; - ags = pkgs.ags_1.overrideAttrs (_: prev: { - buildInputs = - prev.buildInputs - ++ [ - pkgs.libdbusmenu-gtk3 - ]; - }); -in { - home.packages = with pkgs; [ - ags - # TODO: Find a way to add this for ags only - covercolors - brightnessctl - blueberry - ]; - # systemd.user.services.ags = { - # Unit = { - # Description = " A customizable and extensible shell "; - # PartOf = systemdTarget; - # Requires = systemdTarget; - # After = systemdTarget; - # }; - # - # Service = { - # Type = "simple"; - # ExecStart = "${ags}/bin/ags"; - # Restart = "always"; - # }; - # - # Install = {WantedBy = [systemdTarget];}; - # }; - - xdg.configFile."ags".source = ./.; -} diff --git a/modules/wm/ags/layouts/bar.js b/modules/wm/ags/layouts/bar.js deleted file mode 100644 index 0e92e65..0000000 --- a/modules/wm/ags/layouts/bar.js +++ /dev/null @@ -1,88 +0,0 @@ -import { Clock } from "../modules/clock.js"; -import * as wm from "../modules/wm.js"; -import * as audio from "../modules/audio.js"; -import * as network from "../modules/network.js"; -import * as bluetooth from "../modules/bluetooth.js"; -import * as battery from "../modules/battery.js"; -import * as notifications from "../modules/notifications.js"; -import * as mpris from "../modules/mpris.js"; - -/** - *@param {number} monitor - */ -export const Bar = (monitor) => - Widget.Window({ - monitor, - name: `bar${monitor}`, - className: "transparent", - exclusivity: "exclusive", - anchor: ["top", "left", "right"], - layer: "bottom", - child: Widget.CenterBox({ - // startWidget: Widget.Box({ - // children: [ - // wm.Tags({ - // monitor, - // labels: ["一", "二", "三", "四", "五", "六", "七", "八", "九"], - // }), - // wm.Layout({ monitor }), - // wm.ClientLabel({ monitor }), - // ], - // }), - centerWidget: Widget.Box({ - hpack: "center", - children: [ - Widget.Button({ - css: "min-width: 200px;", - onClicked: () => App.toggleWindow("notifications"), - child: notifications.Indicator({ - hexpand: true, - hpack: "center", - }), - }), - ], - }), - endWidget: Widget.Box({ - hpack: "end", - children: [ - Widget.Box({ - className: "module", - css: "margin-right: 48px", - visible: mpris.activePlayer.bind().as((x) => !!x), - children: mpris.activePlayer - .bind() - .as((player) => (player ? [mpris.LinePlayer({ player })] : [])), - }), - Widget.Button({ - onClicked: () => App.toggleWindow("quicksettings"), - className: "module quicksettings", - child: Widget.Box({ - children: [ - audio.MicrophoneIndicator({ - className: "qs-item", - }), - notifications.DNDIndicator({ - className: "qs-item", - }), - network.Indicator({ className: "qs-item" }), - audio.VolumeIndicator({ className: "qs-item" }), - bluetooth.Indicator({ - hideIfDisabled: true, - className: "qs-item", - }), - battery.Indicator({ className: "qs-item" }), - ], - }), - }).hook(App, (self, win, visible) => { - self.toggleClassName("active", win === "quicksettings" && visible); - }), - Clock({ format: "%a %d %b", className: "module bold" }), - Clock({ - format: "%H:%M", - className: "module accent bold", - css: "margin-right: 0px", - }), - ], - }), - }), - }); diff --git a/modules/wm/ags/layouts/notifications.js b/modules/wm/ags/layouts/notifications.js deleted file mode 100644 index 68e7a1e..0000000 --- a/modules/wm/ags/layouts/notifications.js +++ /dev/null @@ -1,55 +0,0 @@ -import PopupWindow from "../misc/popup.js"; -import * as notifications from "../modules/notifications.js"; - -const notificationsService = await Service.import("notifications"); - -const Header = () => - Widget.Box({ - hexpand: true, - css: "margin-bottom: 40px", - children: [ - notifications.DNDToggle({ - className: "surface p10 round", - css: ` - min-width: 24px; - min-height: 24px; - `, - hpack: "start", - hexpand: true, - }), - notifications.ClearButton({ - hpack: "end", - hexpand: true, - }), - ], - }); - -export const Notifications = () => - PopupWindow({ - name: "notifications", - exclusivity: "exclusive", - transition: "slide_down", - layout: "top-center", - duration: 300, - child: Widget.Box({ - vertical: true, - className: "bgcont", - css: ` - min-width: 600px; - margin: 10px; - padding: 12px; - border-radius: 20px; - `, - children: - /** @type {any} */ - ( - notificationsService - .bind("notifications") - .as((x) => - x.length > 0 - ? [Header(), notifications.List({})] - : [Header(), notifications.Placeholder({})], - ) - ), - }), - }); diff --git a/modules/wm/ags/layouts/osd.js b/modules/wm/ags/layouts/osd.js deleted file mode 100644 index ac98a1a..0000000 --- a/modules/wm/ags/layouts/osd.js +++ /dev/null @@ -1,85 +0,0 @@ -import { getIcon } from "../modules/audio.js"; -import brightness from "../services/brightness.js"; - -const audio = await Service.import("audio"); - -const DELAY = 1000; - -function OnScreenProgress() { - const indicator = Widget.Icon({ - vpack: "start", - hpack: "center", - size: 30, - css: "padding-right: 12px;", - }); - const progress = Widget.Slider({ - drawValue: false, - hexpand: true, - }); - const revealer = Widget.Revealer({ - transition: "crossfade", - css: "opacity: 0", - revealChild: true, - vpack: "center", - hpack: "center", - child: Widget.Box({ - vpack: "center", - hpack: "center", - className: "osd bgcount", - css: "padding: 20px;", - children: [indicator, progress], - }), - }); - // Prevent OSD to be shown when starting ags. - Utils.timeout(DELAY * 2, () => { - revealer.css = "opacity: 1"; - }); - - let count = 0; - /** - * @param {number} value - * @param {string} icon - */ - function show(value, icon) { - revealer.reveal_child = true; - indicator.icon = icon; - progress.value = value; - count++; - Utils.timeout(DELAY, () => { - count--; - if (count === 0) revealer.reveal_child = false; - }); - } - return revealer - .hook( - brightness, - () => show(brightness.screen, "display-brightness-symbolic"), - "notify::screen", - ) - .hook( - audio.speaker, - () => show(audio.speaker.volume, getIcon(audio.speaker.volume * 100)), - "notify::volume", - ) - .hook( - audio.speaker, - () => - show( - audio.speaker.is_muted ? 0 : audio.speaker.volume, - audio.speaker.is_muted - ? "audio-volume-muted-symbolic" - : getIcon(audio.speaker.volume * 100), - ), - "notify::is-muted", - ); -} - -export const OSD = () => - Widget.Window({ - name: "osd", - className: "indicator", - layer: "overlay", - clickThrough: true, - anchor: ["bottom"], - child: OnScreenProgress(), - }); diff --git a/modules/wm/ags/layouts/quicksettings.js b/modules/wm/ags/layouts/quicksettings.js deleted file mode 100644 index 69a6f75..0000000 --- a/modules/wm/ags/layouts/quicksettings.js +++ /dev/null @@ -1,160 +0,0 @@ -import Gtk from "gi://Gtk?version=3.0"; -import * as audio from "../modules/audio.js"; -import * as brightness from "../modules/brightness.js"; -import * as network from "../modules/network.js"; -import * as bluetooth from "../modules/bluetooth.js"; -import * as darkmode from "../modules/darkmode.js"; -import * as powerprofile from "../modules/powerprofile.js"; -// import * as nightmode from "../modules/nightmode.js"; -import * as mpris from "../modules/mpris.js"; -import * as systray from "../modules/systray.js"; -import PopupWindow from "../misc/popup.js"; -import { opened, Menu } from "../misc/menu.js"; - -const mprisService = await Service.import("mpris"); - -/** - * @param {Array} toggles - * @param {Array} menus - */ -const Row = (toggles = [], menus = []) => - Widget.Box({ - vertical: true, - children: [ - Widget.Box({ - homogeneous: true, - children: toggles, - }), - ...menus, - ], - }); - -const Header = () => - Widget.Box({ - vertical: true, - children: [ - Widget.Box({ - css: "margin-bottom: 12px", - children: [ - Widget.Box({ - className: "avatar", - css: `background-image: url("/home/${Utils.USER}/.face");`, - }), - Widget.Box({ hexpand: true }), - Widget.Button({ - child: Widget.Icon("emblem-system-symbolic"), - onClicked: () => { - Utils.execAsync("gnome-control-center"); - App.closeWindow("quicksettings"); - }, - vpack: "center", - className: "surface sys-button", - }), - Widget.Button({ - child: Widget.Icon("system-log-out-symbolic"), - onClicked: () => { - opened.value = opened.value === "sleep" ? "" : "sleep"; - }, - vpack: "center", - css: "margin: 12px", - className: "surface sys-button", - }), - Widget.Button({ - child: Widget.Icon("system-shutdown-symbolic"), - onClicked: () => { - opened.value = opened.value === "shutdown" ? "" : "shutdown"; - }, - vpack: "center", - className: "surface sys-button", - }), - ], - }), - VerificationMenu({ - name: "sleep", - icon: "system-log-out-symbolic", - title: "Hybernate?", - command: "systemctl suspend --now", - }), - VerificationMenu({ - name: "shutdown", - icon: "system-shutdown-symbolic", - title: "Shutdown?", - command: "shutdown now", - }), - ], - }); - -/** @param {{ - * name: string, - * icon: string, - * title: string, - * command: string, - * }} props */ -const VerificationMenu = ({ name, icon, title, command }) => - Menu({ - name, - icon: Widget.Icon(icon), - title: title, - content: [ - Widget.Button({ - onClicked: () => { - opened.value = ""; - App.closeWindow("quicksettings"); - Utils.execAsync(command); - }, - child: Widget.Label({ - label: "Yes", - hpack: "start", - css: "margin-left: 12px", - }), - }), - Widget.Button({ - onClicked: () => { - opened.value = ""; - }, - child: Widget.Label({ - label: "No", - hpack: "start", - css: "margin-left: 12px", - }), - }), - ], - }); - -export const Quicksettings = () => - PopupWindow({ - name: "quicksettings", - exclusivity: "exclusive", - transition: "slide_down", - layout: "top-right", - duration: 300, - child: Widget.Box({ - vertical: true, - className: "bgcont qs-container", - children: [ - Header(), - Row( - [audio.Volume({ type: "speaker" })], - [audio.SinkSelector({}), audio.AppMixer({})], - ), - brightness.Brightness({}), - Row( - [network.Toggle({}), bluetooth.Toggle({})], - [network.Selection({}), bluetooth.Selection({})], - ), - Widget.Box({ - homogeneous: true, - children: [darkmode.Toggle(), audio.MuteToggle({})], - }), - Row( - [systray.Toggle({}), powerprofile.Toggle({})], - [systray.Selection({}), powerprofile.Selection({})], - ), - Widget.Box({ - children: mpris.activePlayer - .bind() - .as((player) => player ? [mpris.MprisPlayer({ player })] : []), - }), - ], - }), - }); diff --git a/modules/wm/ags/misc/circular-progress.js b/modules/wm/ags/misc/circular-progress.js deleted file mode 100644 index 89f1ff0..0000000 --- a/modules/wm/ags/misc/circular-progress.js +++ /dev/null @@ -1,166 +0,0 @@ -const { Gtk } = imports.gi; -const Lang = imports.lang; -import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; - -// -- Styling -- -// min-height for diameter -// min-width for trough stroke -// padding for space between trough and progress -// margin for space between widget and parent -// background-color for trough color -// color for progress color -// -- Usage -- -// font size for progress value (0-100px) (hacky i know, but i want animations) -export const AnimatedCircProg = ({ - initFrom = 0, - initTo = 0, - initAnimTime = 2900, - initAnimPoints = 1, - extraSetup = () => {}, - ...rest -}) => - Widget.DrawingArea({ - ...rest, - css: `${ - initFrom !== initTo - ? `font-size: ${initFrom}px; transition: ${initAnimTime}ms linear;` - : "" - }`, - setup: (area) => { - const styleContext = area.get_style_context(); - const width = styleContext.get_property( - "min-height", - Gtk.StateFlags.NORMAL, - ); - const height = styleContext.get_property( - "min-height", - Gtk.StateFlags.NORMAL, - ); - const padding = styleContext.get_padding(Gtk.StateFlags.NORMAL).left; - const marginLeft = styleContext.get_margin(Gtk.StateFlags.NORMAL).left; - const marginRight = styleContext.get_margin(Gtk.StateFlags.NORMAL).right; - const marginTop = styleContext.get_margin(Gtk.StateFlags.NORMAL).top; - const marginBottom = styleContext.get_margin( - Gtk.StateFlags.NORMAL, - ).bottom; - area.set_size_request( - width + marginLeft + marginRight, - height + marginTop + marginBottom, - ); - area.connect( - "draw", - Lang.bind(area, (area, cr) => { - const styleContext = area.get_style_context(); - const width = styleContext.get_property( - "min-height", - Gtk.StateFlags.NORMAL, - ); - const height = styleContext.get_property( - "min-height", - Gtk.StateFlags.NORMAL, - ); - const padding = styleContext.get_padding(Gtk.StateFlags.NORMAL).left; - const marginLeft = styleContext.get_margin( - Gtk.StateFlags.NORMAL, - ).left; - const marginRight = styleContext.get_margin( - Gtk.StateFlags.NORMAL, - ).right; - const marginTop = styleContext.get_margin(Gtk.StateFlags.NORMAL).top; - const marginBottom = styleContext.get_margin( - Gtk.StateFlags.NORMAL, - ).bottom; - area.set_size_request( - width + marginLeft + marginRight, - height + marginTop + marginBottom, - ); - - const progressValue = - styleContext.get_property("font-size", Gtk.StateFlags.NORMAL) / - 100.0; - - const bg_stroke = styleContext.get_property( - "min-width", - Gtk.StateFlags.NORMAL, - ); - const fg_stroke = bg_stroke - padding; - const radius = - Math.min(width, height) / 2.0 - - Math.max(bg_stroke, fg_stroke) / 2.0; - const center_x = width / 2.0 + marginLeft; - const center_y = height / 2.0 + marginTop; - const start_angle = -Math.PI / 2.0; - const end_angle = start_angle + 2 * Math.PI * progressValue; - const start_x = center_x + Math.cos(start_angle) * radius; - const start_y = center_y + Math.sin(start_angle) * radius; - const end_x = center_x + Math.cos(end_angle) * radius; - const end_y = center_y + Math.sin(end_angle) * radius; - - // Draw background - const background_color = styleContext.get_property( - "background-color", - Gtk.StateFlags.NORMAL, - ); - cr.setSourceRGBA( - background_color.red, - background_color.green, - background_color.blue, - background_color.alpha, - ); - cr.arc(center_x, center_y, radius, 0, 2 * Math.PI); - cr.setLineWidth(bg_stroke); - cr.stroke(); - - if (progressValue == 0) return; - - // Draw progress - const color = styleContext.get_property( - "color", - Gtk.StateFlags.NORMAL, - ); - cr.setSourceRGBA(color.red, color.green, color.blue, color.alpha); - cr.arc(center_x, center_y, radius, start_angle, end_angle); - cr.setLineWidth(fg_stroke); - cr.stroke(); - - // Draw rounded ends for progress arcs - cr.setLineWidth(0); - cr.arc(start_x, start_y, fg_stroke / 2, 0, 0 - 0.01); - cr.fill(); - cr.arc(end_x, end_y, fg_stroke / 2, 0, 0 - 0.01); - cr.fill(); - }), - ); - - // Init animation - if (initFrom != initTo) { - area.css = `font-size: ${initFrom}px; transition: ${initAnimTime}ms linear;`; - Utils.timeout( - 20, - () => { - area.css = `font-size: ${initTo}px;`; - }, - area, - ); - const transitionDistance = initTo - initFrom; - const oneStep = initAnimTime / initAnimPoints; - area.css = ` - font-size: ${initFrom}px; - transition: ${oneStep}ms linear; - `; - for (let i = 0; i < initAnimPoints; i++) { - Utils.timeout(Math.max(10, i * oneStep), () => { - if (!area) return; - area.css = `${ - initFrom != initTo - ? "font-size: " + - (initFrom + (transitionDistance / initAnimPoints) * (i + 1)) + - "px;" - : "" - }`; - }); - } - } else area.css = "font-size: 0px;"; - extraSetup(area); - }, - }); diff --git a/modules/wm/ags/misc/menu.js b/modules/wm/ags/misc/menu.js deleted file mode 100644 index 0f945d5..0000000 --- a/modules/wm/ags/misc/menu.js +++ /dev/null @@ -1,186 +0,0 @@ -import GObject from "gi://GObject?version=2.0"; -import Gtk from "gi://Gtk?version=3.0"; - -export const opened = Variable(""); -App.connect("window-toggled", (_, name, visible) => { - if (name === "quicksettings" && !visible) - Utils.timeout(500, () => { - opened.value = ""; - }); -}); - -/** - * @param {{ - * name: string, - * activate?: false | (() => void), - * } & import("../types/widgets/button").ButtonProps} props - */ -export const Arrow = ({ name, activate, ...props }) => { - let deg = 0; - let iconOpened = false; - const icon = Widget.Icon("pan-end-symbolic").hook(opened, () => { - if ( - (opened.value === name && !iconOpened) || - (opened.value !== name && iconOpened) - ) { - const step = opened.value === name ? 10 : -10; - iconOpened = !iconOpened; - for (let i = 0; i < 9; ++i) { - Utils.timeout(15 * i, () => { - deg += step; - icon.setCss(`-gtk-icon-transform: rotate(${deg}deg);`); - }); - } - } - }); - return Widget.Button({ - child: icon, - className: "qs-icon", - onClicked: () => { - opened.value = opened.value === name ? "" : name; - if (typeof activate === "function") activate(); - }, - ...props, - }); -}; - -/** - * @typedef {{ - * name: string, - * icon: Gtk.Widget, - * label: Gtk.Widget, - * activate: () => void - * deactivate: () => void - * activateOnArrow?: boolean - * connection: [GObject.Object, () => boolean] - * } & import("../types/widgets/box").BoxProps} ArrowToggleButtonProps - * @param {ArrowToggleButtonProps} props - */ -export const ArrowToggleButton = ({ - name, - icon, - label, - activate, - deactivate, - activateOnArrow = true, - connection: [service, condition], -}) => - Widget.Box({ - className: "qs-button surface", - setup: (self) => - self.hook(service, () => { - self.toggleClassName("accent", condition()); - }), - children: [ - Widget.Button({ - child: Widget.Box({ - hexpand: true, - children: [icon, label], - }), - onClicked: () => { - if (condition()) { - deactivate(); - if (opened.value === name) opened.value = ""; - } else { - activate(); - } - }, - }), - Arrow({ name, activate: activateOnArrow && activate }), - ], - }); - -/** - * @typedef {{ - * icon: Gtk.Widget, - * label: Gtk.Widget, - * activate: () => void - * deactivate: () => void - * connection: [GObject.Object, () => boolean] - * } & import("../types/widgets/box").BoxProps} SimpleToggleButtonProps - * @param {SimpleToggleButtonProps} props - */ -export const SimpleToggleButton = ({ - icon, - label, - activate, - deactivate, - connection: [service, condition], -}) => - Widget.Box({ - className: "qs-button surface", - setup: (self) => - self.hook(service, () => { - self.toggleClassName("accent", condition()); - }), - children: [ - Widget.Button({ - child: Widget.Box({ - hexpand: true, - children: [icon, label], - }), - onClicked: () => { - if (condition()) { - deactivate(); - } else { - activate(); - } - }, - }), - ], - }); - -/** - * @typedef {{ - * name: string, - * icon: Gtk.Widget, - * title: string, - * content: Gtk.Widget[], - * } & import("../types/widgets/revealer").RevealerProps} MenuProps - * @param {MenuProps} props - */ -export const Menu = ({ name, icon, title, content, ...props }) => - Widget.Revealer({ - transition: "slide_down", - reveal_child: opened.bind().as((v) => v === name), - child: Widget.Box({ - className: "qs-submenu surface", - vertical: true, - children: [ - Widget.Box({ - className: "qs-sub-title accent", - children: [ - icon, - Widget.Label({ - className: "bold f16", - truncate: "end", - label: title, - }), - ], - }), - Widget.Box({ - vertical: true, - className: "qs-sub-content", - children: content, - }), - ], - }), - ...props, - }); - -/** @param {{type?: string, command?: string} & import("../types/widgets/button").ButtonProps} props */ -export const SettingsButton = ({ type, command, ...props }) => - Widget.Button({ - onClicked: () => { - Utils.execAsync(command ?? `gnome-control-center ${type}`); - App.closeWindow("quicksettings"); - }, - hexpand: true, - child: Widget.Box({ - children: [ - Widget.Icon("emblem-system-symbolic"), - Widget.Label("Settings"), - ], - }), - ...props, - }); diff --git a/modules/wm/ags/misc/popup.js b/modules/wm/ags/misc/popup.js deleted file mode 100644 index 4eea9cf..0000000 --- a/modules/wm/ags/misc/popup.js +++ /dev/null @@ -1,198 +0,0 @@ -// Stollen from https://github.com/Aylur/dotfiles/blob/main/ags/widget/PopupWindow.ts# - -/** @typedef {import('../types/widgets/window').WindowProps} WindowProps */ -/** @typedef {import('../types/widgets/revealer').RevealerProps} RevealerProps */ -/** @typedef {import('../types/widgets/eventbox').EventBoxProps} EventBoxProps */ -/** @typedef {import('gi://Gtk?version=3.0')} Gtk */ - -/** - * @param {string} name - * @param {EventBoxProps} - * @returns {any} - */ -export const Padding = ( - name, - { css = "", hexpand = true, vexpand = true } = {}, -) => - // Widget.Box({}); - Widget.EventBox({ - hexpand, - vexpand, - can_focus: false, - child: Widget.Box({ css }), - setup: (w) => w.on("button-press-event", () => App.toggleWindow(name)), - }); - -/** - * @param {string} name - * @param {Child} child - * @param {Transition} [transition="slide_down"] - * @param {number} duration - */ -const PopupRevealer = ( - name, - child, - transition = "slide_down", - duration = 500, -) => - Widget.Box( - { css: "padding: 1px;" }, - Widget.Revealer({ - transition, - child: Widget.Box({ - class_name: "window-content", - child, - }), - transitionDuration: duration, - setup: (self) => - self.hook(App, (_, wname, visible) => { - if (wname === name) self.reveal_child = visible; - }), - }), - ); -/** - * @param {string} name - * @param {Child} child - * @param {Transition} [transition] - * @param {number} [duration] - * @returns {{ center: () => any; top: () => any; "top-right": () => any; "top-center": () => any; "top-left": () => any; "bottom-left": () => any; "bottom-center": () => any; "bottom-right": () => any; }} - */ -const Layout = (name, child, transition, duration) => ({ - center: () => - Widget.CenterBox( - {}, - Padding(name), - Widget.CenterBox( - { vertical: true }, - Padding(name), - PopupRevealer(name, child, transition, duration), - Padding(name), - ), - Padding(name), - ), - top: () => - Widget.CenterBox( - {}, - Padding(name), - Widget.Box( - { vertical: true }, - PopupRevealer(name, child, transition, duration), - Padding(name), - ), - Padding(name), - ), - "top-right": () => - Widget.Box( - {}, - Padding(name), - Widget.Box( - { - hexpand: false, - vertical: true, - }, - PopupRevealer(name, child, transition, duration), - Padding(name), - ), - ), - "top-center": () => - Widget.Box( - {}, - Padding(name), - Widget.Box( - { - hexpand: false, - vertical: true, - }, - PopupRevealer(name, child, transition, duration), - Padding(name), - ), - Padding(name), - ), - "top-left": () => - Widget.Box( - {}, - Widget.Box( - { - hexpand: false, - vertical: true, - }, - PopupRevealer(name, child, transition, duration), - Padding(name), - ), - Padding(name), - ), - "bottom-left": () => - Widget.Box( - {}, - Widget.Box( - { - hexpand: false, - vertical: true, - }, - Padding(name), - PopupRevealer(name, child, transition, duration), - ), - Padding(name), - ), - "bottom-center": () => - Widget.Box( - {}, - Padding(name), - Widget.Box( - { - hexpand: false, - vertical: true, - }, - Padding(name), - PopupRevealer(name, child, transition, duration), - ), - Padding(name), - ), - "bottom-right": () => - Widget.Box( - {}, - Padding(name), - Widget.Box( - { - hexpand: false, - vertical: true, - }, - Padding(name), - PopupRevealer(name, child, transition, duration), - ), - ), -}); - -/** @typedef {RevealerProps["transition"]} Transition */ -/** @typedef {WindowProps["child"]} Child */ -/** - * @typedef {Omit & { - * name: string - * layout?: keyof ReturnType - * transition?: Transition, - * duration?: number - * }} PopupWindowProps - * @param {PopupWindowProps} props - */ -export default ({ - name, - child, - layout = "center", - transition, - exclusivity = "ignore", - duration, - ...props -}) => - Widget.Window({ - name, - class_names: [name, "popup-window"], - setup: (w) => w.keybind("Escape", () => App.closeWindow(name)), - visible: false, - keymode: "on-demand", - exclusivity, - layer: "top", - // anchor: ["top", "right"], - anchor: ["top", "bottom", "right", "left"], - child: Layout(name, child, transition, duration)[layout](), - ...props, - }); diff --git a/modules/wm/ags/misc/utils.js b/modules/wm/ags/misc/utils.js deleted file mode 100644 index 321acdf..0000000 --- a/modules/wm/ags/misc/utils.js +++ /dev/null @@ -1,32 +0,0 @@ -import GLib from "gi://GLib?version=2.0"; - -/** - * @param {string | null | undefined} name - * @param {string | null | undefined} [fallback] - */ -export function icon(name, fallback) { - if (!name) return fallback || ""; - if (typeof name !== "string") return name; - - const sub = substitutes[name]; - if (sub && Utils.lookUpIcon(sub)) return sub; - if (Utils.lookUpIcon(name)) return name; - - if (GLib.file_test(name, GLib.FileTest.EXISTS)) return name; - - return fallback || ""; -} - -export const substitutes = { - "transmission-gtk": "transmission", - "blueberry.py": "blueberry", - Caprine: "facebook-messenger", - "com.raggesilver.BlackBox-symbolic": "terminal-symbolic", - "org.wezfurlong.wezterm-symbolic": "terminal-symbolic", - "audio-headset-bluetooth": "audio-headphones-symbolic", - "audio-card-analog-usb": "audio-speakers-symbolic", - "audio-card-analog-pci": "audio-card-symbolic", - "preferences-system": "emblem-system-symbolic", - "com.github.Aylur.ags-symbolic": "controls-symbolic", - "com.github.Aylur.ags": "controls-symbolic", -}; diff --git a/modules/wm/ags/modules/audio.js b/modules/wm/ags/modules/audio.js deleted file mode 100644 index 44bbf13..0000000 --- a/modules/wm/ags/modules/audio.js +++ /dev/null @@ -1,228 +0,0 @@ -import { icon } from "../misc/utils.js"; -import { - Arrow, - Menu, - SettingsButton, - SimpleToggleButton, -} from "../misc/menu.js"; - -const audio = await Service.import("audio"); - -const volumeIcons = /** @type {const} */ ([ - [101, "overamplified"], - [67, "high"], - [34, "medium"], - [1, "low"], - [0, "muted"], -]); - -/** @param {number} volume */ -export const getIcon = (volume) => { - const icon = volumeIcons.find(([threshold]) => threshold <= volume)?.[1]; - return `audio-volume-${icon}-symbolic`; -}; - -/** @param {{type?: "speaker" | "microphone"} & import("../types/widgets/icon.js").IconProps} props */ -export const VolumeIndicator = ({ type = "speaker", ...props }) => - Widget.Icon(props).hook(audio, (self) => { - if (audio[type].is_muted) { - self.icon = "audio-volume-muted-symbolic"; - self.tooltip_text = "Muted"; - return; - } - const vol = audio[type].volume * 100; - self.icon = getIcon(vol); - self.tooltip_text = `Volume: ${Math.floor(vol)}%`; - }); - -/** @param {import("../types/widgets/icon.js").IconProps} props */ -export const MicrophoneIndicator = (props) => - Widget.Icon(props).hook(audio, (self) => { - self.visible = audio.microphone.is_muted || audio.recorders.length > 0; - if (audio.microphone.is_muted) self.icon = "microphone-disabled-symbolic"; - else if (audio.recorders.length > 0) - self.icon = "microphone-sensitivity-high-symbolic"; - }); - -/** @param {{type?: "speaker" | "microphone"} & import("../types/widgets/slider.js").SliderProps} props */ -const VolumeSlider = ({ type = "speaker", ...props }) => - Widget.Slider({ - hexpand: true, - drawValue: false, - onChange: ({ value, dragging }) => { - if (dragging) { - audio[type].volume = value; - audio[type].is_muted = false; - } - }, - value: audio[type].bind("volume"), - css: audio[type].bind("is_muted").as((x) => `opacity: ${x ? 0.7 : 1}`), - ...props, - }); - -/** @param {{type?: "speaker" | "microphone"} & import("../types/widgets/box.js").BoxProps} props */ -export const Volume = ({ type = "speaker", ...props }) => - Widget.Box({ - className: "qs-slider", - children: [ - Widget.Button({ - onClicked: () => { - audio[type].is_muted = !audio[type].is_muted; - }, - child: VolumeIndicator({ type }), - }), - VolumeSlider({ type }), - Widget.Label({ - label: audio[type].bind("volume").as( - (vol) => - `${Math.floor(vol * 100) - .toString() - .padStart(3)}%`, - ), - }), - Widget.Box({ - vpack: "center", - child: Arrow({ name: "sink-selector" }), - }), - Widget.Box({ - vpack: "center", - child: Arrow({ name: "app-mixer" }), - visible: audio.bind("apps").as((a) => a.length > 0), - }), - ], - ...props, - }); - -/** @param {Partial} props */ -export const MuteToggle = ({ ...props } = {}) => - SimpleToggleButton({ - icon: Widget.Icon({ - className: "qs-icon", - icon: audio.microphone - .bind("is_muted") - .as((x) => - x - ? "microphone-disabled-symbolic" - : "microphone-sensitivity-high-symbolic", - ), - }), - label: Widget.Label({ - label: audio.microphone - .bind("is_muted") - .as((x) => (x ? "Unmute" : "Mute")), - }), - activate: () => { - audio.microphone.is_muted = true; - }, - deactivate: () => { - audio.microphone.is_muted = false; - }, - connection: [audio.microphone, () => audio.microphone.is_muted || false], - ...props, - }); - -/** @param {Partial} props */ -export const SinkSelector = (props) => - Menu({ - name: "sink-selector", - icon: Widget.Icon("audio-headphones-symbolic"), - title: "Sink Selector", - content: [ - Widget.Box({ - className: "qs-sub-sub-content", - vertical: true, - children: audio.bind("speakers").as((a) => a.map(SinkItem)), - }), - Widget.Separator({ className: "accent" }), - SettingsButton({ type: "sound" }), - ], - ...props, - }); - -/** @param {import("../types/service/audio.js").Stream} stream */ -const SinkItem = (stream) => - Widget.Button({ - hexpand: true, - onClicked: () => { - audio.speaker = stream; - }, - child: Widget.Box({ - children: [ - Widget.Icon({ - icon: icon(stream.icon_name, "audio-x-generic-symbolic"), - tooltip_text: stream.icon_name || "", - }), - Widget.Label({ - label: (stream.description || "").split(" ").slice(0, 4).join(" "), - }), - Widget.Icon({ - icon: "object-select-symbolic", - hexpand: true, - hpack: "end", - visible: audio.speaker.bind("stream").as((s) => s === stream.stream), - }), - ], - }), - }); - -/** @param {Partial} props */ -export const AppMixer = (props) => - Menu({ - name: "app-mixer", - icon: Widget.Icon("audio-volume-high-symbolic"), - title: "App Mixer", - content: [ - Widget.Box({ - vertical: true, - className: "qs-sub-sub-content", - children: audio.bind("apps").as((a) => a.map(MixerItem)), - }), - Widget.Separator({ className: "accent" }), - SettingsButton({ type: "sound" }), - ], - ...props, - }); - -/** @param {import("../types/service/audio.js").Stream} stream */ -const MixerItem = (stream) => - Widget.Box({ - hexpand: true, - children: [ - Widget.Icon({ - tooltipText: stream.bind("name").as((n) => n || ""), - icon: stream - .bind("name") - .as((n) => - n && Utils.lookUpIcon(n) ? n : "audio-x-generic-symbolic", - ), - }), - Widget.Box({ - vertical: true, - children: [ - Widget.Label({ - xalign: 0, - truncate: "end", - max_width_chars: 28, - label: stream.bind("description").as((d) => d || ""), - }), - Widget.Slider({ - hexpand: true, - draw_value: false, - value: stream.bind("volume"), - onChange: ({ value }) => { - stream.volume = value; - }, - }), - ], - }), - Widget.Label({ - css: "padding: 12px", - label: stream.bind("volume").as( - (x) => - `${Math.floor(x * 100) - .toString() - .padStart(3)}%`, - ), - }), - ], - }); diff --git a/modules/wm/ags/modules/battery.js b/modules/wm/ags/modules/battery.js deleted file mode 100644 index cc721b0..0000000 --- a/modules/wm/ags/modules/battery.js +++ /dev/null @@ -1,28 +0,0 @@ -const battery = await Service.import("battery"); - -/** @param {import("../types/widgets/box").BoxProps} props */ -export const Indicator = ({ ...props }) => - Widget.Box({ - children: [ - Widget.Icon({ - icon: battery.bind("icon_name"), - className: Utils.merge( - [ - battery.bind("charging"), - battery.bind("charged"), - battery.bind("percent"), - ], - (charging, charged, percent) => { - if (charging || charged) return "green"; - if (percent < 30) return "red"; - return ""; - }, - ), - }), - Widget.Label({ - label: battery.bind("percent").as((x) => `${x}%`), - }), - ], - visible: battery.bind("available"), - ...props, - }); diff --git a/modules/wm/ags/modules/bluetooth.js b/modules/wm/ags/modules/bluetooth.js deleted file mode 100644 index b9b9390..0000000 --- a/modules/wm/ags/modules/bluetooth.js +++ /dev/null @@ -1,94 +0,0 @@ -import { ArrowToggleButton, Menu, SettingsButton } from "../misc/menu.js"; - -const bluetooth = await Service.import("bluetooth"); - -const connected = Utils.merge( - [bluetooth.bind("enabled"), bluetooth.bind("connected_devices")], - (enabled, devices) => enabled && devices.length > 0, -); - -/** @param {{hideIfDisabled?: boolean} & import("../types/widgets/icon.js").IconProps} props */ -export const Indicator = ({ hideIfDisabled = false, ...props } = {}) => - Widget.Icon({ - icon: connected.as( - (x) => `bluetooth-${x ? "active" : "disabled"}-symbolic`, - ), - visible: connected.as((x) => x || !hideIfDisabled), - ...props, - }); - -/** @param {import("../types/widgets/label.js").LabelProps} props */ -export const ConnectedLabel = (props) => - Widget.Label(props).hook(bluetooth, (self) => { - if (!bluetooth.enabled) self.label = "Disabled"; - - if (bluetooth.connected_devices.length === 0) self.label = "Disconnected"; - else if (bluetooth.connected_devices.length === 1) - self.label = bluetooth.connected_devices[0].alias; - else self.label = `${bluetooth.connected_devices.length} Connected`; - }); - -/** @param {Partial} props */ -export const Toggle = (props) => - ArrowToggleButton({ - name: "bluetooth", - icon: Indicator({ className: "qs-icon" }), - label: ConnectedLabel({ - max_width_chars: 20, - }), - activate: () => { - bluetooth.enabled = true; - }, - deactivate: () => { - bluetooth.enabled = false; - }, - connection: [bluetooth, () => bluetooth.enabled], - ...props, - }); - -/** @param {Partial} props */ -export const Selection = (props) => - Menu({ - name: "bluetooth", - icon: Indicator({}), - title: "Bluetooth", - content: [ - Widget.Box({ - vertical: true, - className: "qs-sub-sub-content", - children: bluetooth.bind("devices").as((x) => x.map(DeviceItem)), - }), - Widget.Separator({ className: "accent" }), - SettingsButton({ command: "blueberry" }), - ], - ...props, - }); - -/** @param {import("../types/service/bluetooth.js").BluetoothDevice} device */ -const DeviceItem = (device) => - Widget.Box({ - children: [ - Widget.Icon(`${device.icon_name}-symbolic`), - Widget.Label(device.name), - Widget.Box({ hexpand: true }), - Widget.Label({ - label: `${device.battery_percentage}%`, - css: "padding-right: 24px;", - visible: device.bind("battery_percentage").as((x) => x > 0), - }), - Widget.Spinner({ - active: device.bind("connecting"), - visible: device.bind("connecting"), - }), - Widget.Switch({ - active: device.bind("connected"), - visible: device.bind("connecting").as((p) => !p), - setup: (self) => - // TODO: If connecting to the device failed, reset back the switch to `active: false`. - self.connect("state_set", () => { - device.setConnection(self.active); - return true; - }), - }), - ], - }); diff --git a/modules/wm/ags/modules/brightness.js b/modules/wm/ags/modules/brightness.js deleted file mode 100644 index d639550..0000000 --- a/modules/wm/ags/modules/brightness.js +++ /dev/null @@ -1,38 +0,0 @@ -import brightness from "../services/brightness.js"; - -/** @param {import("../types/widgets/slider.js").SliderProps} props */ -const BrightnessSlider = (props) => - Widget.Slider({ - drawValue: false, - hexpand: true, - value: brightness.bind("screen"), - onChange: ({ value }) => { - brightness.screen = value; - }, - ...props, - }); - -/** @param {import("../types/widgets/box.js").BoxProps} props */ -export const Brightness = (props) => - Widget.Box({ - className: "qs-slider", - children: [ - Widget.Icon({ - vpack: "center", - icon: "display-brightness-symbolic", - tooltipText: brightness - .bind("screen") - .as((x) => `Screen Brightness: ${Math.floor(x * 100)}%`), - }), - BrightnessSlider({}), - Widget.Label({ - label: brightness.bind("screen").as( - (x) => - `${Math.floor(x * 100) - .toString() - .padStart(3)}%`, - ), - }), - ], - ...props, - }); diff --git a/modules/wm/ags/modules/clock.js b/modules/wm/ags/modules/clock.js deleted file mode 100644 index b998872..0000000 --- a/modules/wm/ags/modules/clock.js +++ /dev/null @@ -1,14 +0,0 @@ -import GLib from "gi://GLib"; - -export const clock = Variable(GLib.DateTime.new_now_local(), { - poll: [1000, () => GLib.DateTime.new_now_local()], -}); - -/** - * @param {{format?: string} & import("../types/widgets/label").LabelProps} props - */ -export const Clock = ({ format = "%a %d %b %H:%M ", ...props } = {}) => - Widget.Label({ - ...props, - label: Utils.derive([clock], (c) => c.format(format) || "").bind(), - }); diff --git a/modules/wm/ags/modules/darkmode.js b/modules/wm/ags/modules/darkmode.js deleted file mode 100644 index 94214e4..0000000 --- a/modules/wm/ags/modules/darkmode.js +++ /dev/null @@ -1,57 +0,0 @@ -import Gio from "gi://Gio"; -import { SimpleToggleButton } from "../misc/menu.js"; - -const interfaceXml = ` - - - - - - - - - - -`; -const Darkman = Gio.DBusProxy.makeProxyWrapper(interfaceXml); - -const theme = Variable(/** @type {"light" | "dark"} */ ("light")); - -/** @param {Partial} props */ -export const Toggle = ({ ...props } = {}) => - SimpleToggleButton({ - icon: Widget.Icon({ - className: "qs-icon", - icon: theme - .bind() - .as((x) => - x === "light" - ? "weather-clear-symbolic" - : "weather-clear-night-symbolic", - ), - }), - label: Widget.Label({ - label: theme.bind().as((x) => (x === "light" ? "Light" : "Dark")), - }), - activate: () => theme.setValue("dark"), - deactivate: () => theme.setValue("light"), - connection: [theme, () => theme.value === "dark"], - ...props, - }); - -function init() { - const darkman = Darkman( - Gio.DBus.session, - "nl.whynothugo.darkman", - "/nl/whynothugo/darkman", - ); - - theme.value = darkman.Mode; - theme.connect("changed", () => { - darkman.Mode = theme.value; - }); - darkman.connectSignal("ModeChanged", (_proxy, _senderName, nTheme) => { - theme.value = nTheme[0]; - }); -} -init(); diff --git a/modules/wm/ags/modules/mpris.js b/modules/wm/ags/modules/mpris.js deleted file mode 100644 index e6465f1..0000000 --- a/modules/wm/ags/modules/mpris.js +++ /dev/null @@ -1,290 +0,0 @@ -import { AnimatedCircProg } from "../misc/circular-progress.js"; - -const mpris = await Service.import("mpris"); - -/** @param {{player: import("types/service/mpris").MprisPlayer} & import("../types/widgets/icon").IconProps} props */ -const PlayerIcon = ({ player, ...props }) => - Widget.Icon({ - size: 24, - hpack: "start", - vpack: "start", - tooltipText: player.identity || "", - icon: player.bind("entry").transform((entry) => { - const name = `${entry}-symbolic`; - return Utils.lookUpIcon(name) - ? name - : Utils.lookUpIcon(entry) - ? entry - : "audio-x-generic-symbolic"; - }), - ...props, - }); - -/** @param {{player: import("types/service/mpris").MprisPlayer} & import("../types/widgets/label").LabelProps} props */ -const TitleLabel = ({ player, ...props }) => - Widget.Label({ - wrap: true, - truncate: "end", - hpack: "start", - label: player.bind("track_title"), - ...props, - }); - -/** @param {{player: import("types/service/mpris").MprisPlayer} & import("../types/widgets/label").LabelProps} props */ -const ArtistLabel = ({ player, ...props }) => - Widget.Label({ - wrap: true, - truncate: "end", - hpack: "start", - label: player.bind("track_artists").transform((x) => x.map(y => y.replace(/- Topic$/, "")).join(", ")), - ...props, - }); - -/** @param {{ - * player: import("types/service/mpris").MprisPlayer, - * iconProps?: import("types/widgets/icon").IconProps, - * } & import("types/widgets/button").ButtonProps} props */ -export const PlayPause = ({ player, iconProps = {}, ...props }) => - Widget.Button({ - child: Widget.Icon({ - icon: player.bind("play_back_status").as( - (x) => - ({ - Playing: "media-playback-pause-symbolic", - Paused: "media-playback-start-symbolic", - Stopped: "media-playback-start-symbolic", - })[x], - ), - ...iconProps, - }), - onClicked: () => player.playPause(), - visible: player.bind("can_play"), - ...props, - }); - -/** @param {{player: import("types/service/mpris").MprisPlayer} & import("../types/widgets/button").ButtonProps} props */ -const PreviousButton = ({ player, ...props }) => - Widget.Button({ - child: Widget.Icon({ icon: "media-skip-backward-symbolic" }), - onClicked: () => player.previous(), - visible: player.bind("can_go_prev"), - ...props, - }); - -/** @param {{player: import("types/service/mpris").MprisPlayer} & import("../types/widgets/button").ButtonProps} props */ -const NextButton = ({ player, ...props }) => - Widget.Button({ - child: Widget.Icon({ icon: "media-skip-forward-symbolic" }), - onClicked: () => player.next(), - visible: player.bind("can_go_next"), - ...props, - }); - -/** @param {{player: import("types/service/mpris").MprisPlayer} & import("../types/widgets/slider").SliderProps} props */ -const PositionSlider = ({ player, ...props }) => - Widget.Slider({ - className: "mpris-position-slider", - drawValue: false, - onChange: ({ value }) => { - player.position = value * player.length; - }, - visible: player.bind("length").as((l) => l > 0), - setup: (self) => { - function update() { - const value = player.position / player.length; - self.value = value > 0 ? value : 0; - } - self.hook(player, update); - self.hook(player, update, "position"); - self.poll(1000, update); - }, - ...props, - }); - -/** @param {{player: import("types/service/mpris").MprisPlayer} & import("../types/widgets/box").BoxProps} props */ -const PositionCircle = ({ player, child, ...props }) => - Widget.Box({ - child: Widget.Overlay({ - child: AnimatedCircProg({ - css: ` - min-width: 0.136rem; - min-height: 1.636rem; - padding: 0rem; - `, - vpack: "center", - hpack: "center", - className: "accent-rev", - extraSetup: (self) => { - function update() { - const value = player.position / player.length; - self.css = ` - font-size: ${Math.max(value * 100, 0)}px; - min-width: 0.136rem; - min-height: 1.636rem; - padding: 0rem; - `; - } - self.hook(player, update); - self.hook(player, update, "position"); - self.poll(1000, update); - }, - }), - overlays: [child], - }), - ...props, - }); - -/** @param {{player: import("types/service/mpris").MprisPlayer} & import("../types/widgets/box").BoxProps} props */ -export const LinePlayer = ({ player, ...props }) => - Widget.Box({ - visible: player.bind("play_back_status").as((x) => x !== "Stopped"), - children: [ - PositionCircle({ - player, - child: PlayPause({ player, iconProps: { size: 10 } }), - css: "margin-right: 12px;", - }), - Widget.Label({ - label: Utils.merge( - [player.bind("track_title"), player.bind("track_artists")], - (title, artists) => `${title} - ${artists.map(x => x.replace(/- Topic$/, "")).join(", ")}`, - ), - maxWidthChars: 30, - wrap: true, - truncate: "end", - hpack: "start", - }), - ], - ...props, - }); - -export const activePlayer = Variable(mpris.players[0]); -mpris.connect("player-added", (_, bus) => { - mpris.getPlayer(bus)?.connect("changed", (player) => { - if (player?.play_back_status !== "Stopped") { - activePlayer.value = player || mpris.players[0]; - } else { - activePlayer.value = mpris.players[0]; - } - }); -}); -mpris.connect("player-closed", (_, bus) => { - if (activePlayer.value.bus_name === bus) - activePlayer.value = mpris.players[0]; -}); - -/** @param {{player?: import("types/service/mpris").MprisPlayer | null} & import("../types/widgets/box").BoxProps} props */ -export const MprisPlayer = ({ player, ...props }) => { - if (!player) return Widget.Box({ visible: false }); - const colors = getMaterialColors(player); - - return Widget.Box({ - visible: player.bind("play_back_status").as((x) => x !== "Stopped"), - className: `mpris-cover-art ${props.className}`, - css: Utils.merge( - [colors.bind(), player.bind("cover_path")], - (colors, cover) => ` - background-image: radial-gradient(circle, rgba(0, 0, 0, 0.4) 30%, ${colors.primary}), url("${cover}"); \ - color: ${colors.onBackground}; - `, - ), - vexpand: false, - children: [ - Widget.CenterBox({ - vertical: true, - hexpand: true, - startWidget: Widget.Box({ - vertical: true, - vexpand: true, - css: colors.bind().as((x) => `color: ${x.onBackground}`), - child: PlayerIcon({ player }), - }), - centerWidget: Widget.Box({ - hexpand: true, - children: [ - Widget.Box({ - css: colors.bind().as( - (x) => ` - color: ${x.onBackground}; - margin-right: 12px; - `, - ), - vertical: true, - vpack: "center", - hexpand: true, - children: [ - TitleLabel({ - player, - css: "font-weight: 600; font-size: 19px;", - maxWidthChars: 33, - }), - ArtistLabel({ - player, - css: "font-weight: 400; font-size: 17px;", - maxWidthChars: 40, - }), - ], - }), - Widget.Box({ - children: [ - PlayPause({ - player, - hpack: "end", - className: "mpris-play", - css: colors.bind().as( - (x) => ` - background-color: ${x.primary}; - color: ${x.onPrimary}; - `, - ), - }), - ], - }), - ], - }), - endWidget: Widget.Box({ - css: colors.bind().as((x) => `color: ${x.onBackground}`), - vpack: "end", - children: [ - Widget.Box({ - hexpand: true, - children: [ - PreviousButton({ player, css: "margin-left: 16px;" }), - PositionSlider({ player, hexpand: true }), - NextButton({ player, css: "margin-right: 16px;" }), - ], - }), - ], - }), - }), - ], - ...props, - }); -}; - -// TODO: Move ret inside getMaterialColors to support multiple players. -const ret = Variable({ - primary: "#222222", - onPrimary: "#ffffff", - background: "#222222", - onBackground: "#ffffff", -}); -/** @param {import("types/service/mpris").MprisPlayer} player */ -export const getMaterialColors = (player) => { - // TODO: Move that to a hook to allow graceful disconnections - player.connect("changed", (player) => { - const cover = player.cover_path; - // TODO: Wait for the cover to be downloaded, currently we hope that it's ready in <100ms - Utils.timeout(100, () => { - Utils.execAsync(["covercolors", cover]) - .then((colors) => { - const col = JSON.parse(colors); - if (!col) return; - ret.setValue(col); - }) - .catch(print); - }); - }); - - return ret; -}; diff --git a/modules/wm/ags/modules/network.js b/modules/wm/ags/modules/network.js deleted file mode 100644 index 4975704..0000000 --- a/modules/wm/ags/modules/network.js +++ /dev/null @@ -1,114 +0,0 @@ -import { ArrowToggleButton, Menu, SettingsButton } from "../misc/menu.js"; - -const network = await Service.import("network"); - -/** @param {import("../types/widgets/icon.js").IconProps} props*/ -export const Indicator = (props) => - Widget.Icon(props).hook(network, (self) => { - self.icon = - network[network.primary || "wifi"].icon_name ?? - "network-wireless-offline-symbolic"; - }); - -/** @param {import("../types/widgets/label.js").LabelProps} props */ -export const SSIDLabel = (props) => - Widget.Label({ - truncate: "end", - ...props, - }).hook(network, (self) => { - if (network.primary === "wifi") - self.label = network.wifi.ssid || "Not Connected"; - else - self.label = - network.wired.internet !== "disconnected" ? "Wired" : "Not Connected"; - }); - -/** @param {Partial} props */ -export const Toggle = (props) => - ArrowToggleButton({ - name: "network", - icon: Indicator({ className: "qs-icon" }), - label: SSIDLabel({ - max_width_chars: 20, - }), - activate: () => { - network.wifi.enabled = true; - network.wifi.scan(); - }, - deactivate: () => { - network.wifi.enabled = false; - }, - connection: [network.wifi, () => network.wifi.enabled], - ...props, - }); - -/** @param {Partial} props */ -export const Selection = (props) => - Menu({ - name: "network", - icon: Indicator({}), - title: "Network Selection", - content: [ - Wired(), - Widget.Box({ - vertical: true, - className: "qs-sub-sub-content", - children: network.wifi.bind("access_points").as((x) => - x - .sort((a, b) => b.strength - a.strength) - .reduce((acc, x) => { - if (!acc.find((y) => y.ssid === x.ssid)) acc.push(x); - return acc; - }, []) - .slice(0, 10) - .map(WifiItem), - ), - }), - Widget.Separator({ className: "accent" }), - SettingsButton({ type: "wifi" }), - ], - ...props, - }); - -const Wired = () => - Widget.Button({ - // onClicked: - // visible: network.wired.bind("state").as(x => (console.log(x), true)), - child: Widget.Box({ - children: [ - Widget.Icon({ icon: network.wired.bind("icon_name") }), - Widget.Label("Wired"), - Widget.Icon({ - icon: "object-select-symbolic", - hexpand: true, - hpack: "end", - visible: network.bind("primary").as((x) => x === "wired"), - }), - ], - }), - }); - -/** @param {import("../types/service/network.js").Wifi["access_points"][0]} wifi */ -const WifiItem = (wifi) => - Widget.Button({ - onClicked: () => Utils.execAsync(`nmcli device wifi connect ${wifi.bssid}`), - child: Widget.Box({ - children: [ - Widget.Icon(wifi.iconName), - Widget.Label({ - truncate: "end", - max_width_chars: 28, - label: wifi.ssid || "", - }), - Widget.Icon({ - icon: "object-select-symbolic", - hexpand: true, - hpack: "end", - setup: (self) => - Utils.idle(() => { - if (!self.is_destroyed) self.visible = wifi.active; - }), - }), - ], - }), - }); diff --git a/modules/wm/ags/modules/notifications.js b/modules/wm/ags/modules/notifications.js deleted file mode 100644 index 4b74c97..0000000 --- a/modules/wm/ags/modules/notifications.js +++ /dev/null @@ -1,270 +0,0 @@ -const notifications = await Service.import("notifications"); -notifications.popupTimeout = 2000; //in seconds -notifications.forceTimeout = true; //force all notifications to timeout - -/** @param {import("../types/widgets/icon").IconProps} props */ -export const DNDIndicator = (props) => - Widget.Icon({ - visible: notifications.bind("dnd"), - icon: "notifications-disabled-symbolic", - ...props, - }); - -/** @param {import("../types/widgets/button").ButtonProps} props */ -export const DNDToggle = (props) => - Widget.Button({ - onClicked: () => { - notifications.dnd = !notifications.dnd; - }, - child: Widget.Icon({ - icon: notifications - .bind("dnd") - .as((x) => - x - ? "preferences-system-notifications-symbolic" - : "notifications-disabled-symbolic", - ), - }), - className: notifications.bind("dnd").as((x) => (x ? "on" : "")), - ...props, - }); - -/** @param {import("../types/widgets/box").BoxProps} props */ -export const Indicator = ({ ...props }) => - Widget.Box({ - visible: Utils.merge( - [ - notifications.bind("notifications").as((x) => x.length > 0), - notifications.bind("dnd"), - ], - (hasNotif, dnd) => hasNotif && !dnd, - ), - children: [ - Widget.Icon({ icon: "preferences-system-notifications-symbolic" }), - Widget.Revealer({ - transition: "slide_right", - revealChild: notifications.bind("popups").as((x) => x.length > 0), - child: Widget.Label({ - use_markup: true, - truncate: "end", - wrap: false, - label: notifications.bind("popups").as((x) => { - const notif = x[x.length - 1]; - // Keep the text of the old notif for the fade out animation. - if (!notif) return old_notif; - const summary = notif.summary.substring(0, 18).trim(); - const body = notif.body.substring(0, 45).trim(); - old_notif = `${summary}: ${body}`.replaceAll("\n", " "); - return old_notif; - }), - }), - }), - ], - ...props, - }); -let old_notif = ""; - -/** @param {import("../types/service/notifications").Notification} param */ -const NotificationIcon = ({ app_entry, app_icon, image }) => { - if (image) { - return Widget.Box({ - vpack: "start", - hexpand: false, - className: "r20", - css: ` - background-image: url("${image}"); - background-size: cover; - background-repeat: no-repeat; - background-position: center; - min-width: 78px; - min-height: 78px; - margin-right: 12px; - `, - }); - } - - let icon = "dialog-information-symbolic"; - if (Utils.lookUpIcon(app_icon)) icon = app_icon; - if (Utils.lookUpIcon(app_entry || "")) icon = app_entry || ""; - - return Widget.Box({ - vpack: "start", - hexpand: false, - className: "r20", - css: ` - min-width: 78px; - min-height: 78px; - margin-right: 12px; - `, - child: Widget.Icon({ - icon, - size: 58, - hpack: "center", - hexpand: true, - vpack: "center", - vexpand: true, - }), - }); -}; - -import GLib from "gi://GLib"; - -/** @param {number} time */ -const time = (time, format = "%H:%M") => - GLib.DateTime.new_from_unix_local(time).format(format); - -/** @param {import("../types/service/notifications").Notification} notification */ -export const Notification = (notification) => { - const content = Widget.Box({ - children: [ - NotificationIcon(notification), - Widget.Box({ - hexpand: true, - vertical: true, - children: [ - Widget.Box({ - children: [ - Widget.Label({ - css: ` - font-size: 1.1em; - margin-right: 12pt; - `, - xalign: 0, - justification: "left", - hexpand: true, - max_width_chars: 24, - truncate: "end", - wrap: true, - label: notification.summary.trim(), - use_markup: true, - }), - Widget.Label({ - vpack: "start", - label: time(notification.time), - }), - Widget.Button({ - css: ` - margin-left: 6pt; - min-width: 1.2em; - min-height: 1.2em; - `, - vpack: "start", - child: Widget.Icon("window-close-symbolic"), - on_clicked: notification.close, - }), - ], - }), - Widget.Label({ - css: "font-size: .9em;", - hexpand: true, - use_markup: true, - xalign: 0, - justification: "left", - label: notification.body.trim(), - max_width_chars: 24, - wrap: true, - }), - ], - }), - ], - }); - - const actionsbox = - notification.actions.length > 0 - ? Widget.Revealer({ - transition: "slide_down", - child: Widget.EventBox({ - child: Widget.Box({ - class_name: "actions horizontal", - children: notification.actions.map((action) => - Widget.Button({ - className: "button", - on_clicked: () => notification.invoke(action.id), - hexpand: true, - child: Widget.Label(action.label), - }), - ), - }), - }), - }) - : null; - - const eventbox = Widget.EventBox({ - vexpand: false, - on_primary_click: notification.dismiss, - on_hover() { - if (actionsbox) actionsbox.reveal_child = true; - }, - on_hover_lost() { - if (actionsbox) actionsbox.reveal_child = true; - - notification.dismiss(); - }, - child: Widget.Box({ - vertical: true, - children: actionsbox ? [content, actionsbox] : [content], - }), - }); - - return Widget.Box({ - className: "surface r20 p10", - css: "margin: 8px 0;", - child: eventbox, - }); -}; - -/** @param {import("../types/widgets/scrollable").ScrollableProps} props */ -export const List = (props) => - Widget.Scrollable({ - vscroll: "automatic", - hscroll: "never", - css: "min-height: 500px;", - child: Widget.Box({ - vertical: true, - children: notifications - .bind("notifications") - .as((x) => x.map(Notification)), - }), - ...props, - }); - -/** @param {import("../types/widgets/box").BoxProps} props */ -export const Placeholder = (props) => - Widget.Box({ - vertical: true, - vpack: "center", - hpack: "center", - children: [ - Widget.Icon({ - icon: "notifications-disabled-symbolic", - size: 75, - css: "margin: 50px 0", - }), - Widget.Label({ - label: "Your inbox is empty", - css: "margin-bottom: 150px;", - }), - ], - ...props, - }); - -/** @param {import("../types/widgets/button").ButtonProps} props */ -export const ClearButton = (props) => - Widget.Button({ - className: "surface r20 p10", - onClicked: () => notifications.clear(), - sensitive: notifications.bind("notifications").as((x) => x.length > 0), - child: Widget.Box({ - children: [ - Widget.Label({ label: "Clear " }), - Widget.Icon({ - icon: notifications - .bind("notifications") - .as((x) => - x.length > 0 ? "user-trash-full-symbolic" : "user-trash-symbolic", - ), - }), - ], - }), - ...props, - }); diff --git a/modules/wm/ags/modules/powerprofile.js b/modules/wm/ags/modules/powerprofile.js deleted file mode 100644 index 21bbfa1..0000000 --- a/modules/wm/ags/modules/powerprofile.js +++ /dev/null @@ -1,62 +0,0 @@ -import { ArrowToggleButton, Menu } from "../misc/menu.js"; - -const powerProfiles = await Service.import("powerprofiles"); - -/** @param {string} x */ -const capitalize = (x) => x.charAt(0).toUpperCase() + x.slice(1); - -/** @param {Partial} props */ -export const Toggle = (props) => - ArrowToggleButton({ - name: "powerprofile", - icon: Widget.Icon({ - icon: powerProfiles.bind("icon_name"), - className: "qs-icon", - }), - label: Widget.Label({ - label: powerProfiles.bind("active_profile").as(capitalize), - truncate: "end", - max_width_chars: 20, - }), - activate: () => {}, - deactivate: () => {}, - connection: [powerProfiles, () => false], - ...props, - }); - -/** @param {Partial} props */ -export const Selection = (props) => - Menu({ - name: "powerprofile", - icon: Widget.Icon({ icon: powerProfiles.bind("icon_name") }), - title: "Power profile", - // Hard coding the list of profiles since ags does not give them. - content: ["power-saver", "balanced", "performance"].map(ProfileItem), - ...props, - }); - -/** @param {string} profile */ -const ProfileItem = (profile) => - Widget.Button({ - onClicked: () => (powerProfiles.active_profile = profile), - child: Widget.Box({ - children: [ - Widget.Icon({ - icon: `power-profile-${profile}-symbolic`, - }), - Widget.Label({ - label: capitalize(profile), - truncate: "end", - maxWidthChars: 28, - }), - Widget.Icon({ - icon: "object-select-symbolic", - hexpand: true, - hpack: "end", - visible: powerProfiles - .bind("active_profile") - .as((x) => x === profile), - }), - ], - }), - }); diff --git a/modules/wm/ags/modules/systray.js b/modules/wm/ags/modules/systray.js deleted file mode 100644 index 393c555..0000000 --- a/modules/wm/ags/modules/systray.js +++ /dev/null @@ -1,52 +0,0 @@ -import { icon } from "../misc/utils.js"; -import { ArrowToggleButton, Menu } from "../misc/menu.js"; - -const systemtray = await Service.import("systemtray"); - -/** @param {Partial} props */ -export const Toggle = (props) => - ArrowToggleButton({ - name: "systray", - icon: Widget.Icon({ icon: "open-menu-symbolic", className: "qs-icon" }), - label: Widget.Label("Systray"), - activate: () => {}, - deactivate: () => {}, - connection: [systemtray, () => systemtray.items.length > 0], - ...props, - }); - -/** @param {Partial} props */ -export const Selection = (props) => - Menu({ - name: "systray", - icon: Widget.Icon("open-menu-symbolic"), - title: "Systray", - content: [ - Widget.Box({ - vertical: true, - className: "qs-sub-sub-content", - children: systemtray.bind("items").as((i) => i.map(SysTrayItem)), - }), - ], - ...props, - }); - -/** @param {import('../types/service/systemtray.js').TrayItem} item */ -const SysTrayItem = (item) => - Widget.Button({ - css: "margin: 12px;", - child: Widget.Box({ - children: [ - Widget.Icon({ icon: item.bind("icon").as(icon) }), - Widget.Label({ - truncate: "end", - maxWidthChars: 28, - }).hook(item, (self) => { - self.label = item.title || item.tooltip_markup; - }), - ], - }), - tooltipMarkup: item.bind("tooltip_markup"), - onPrimaryClick: (_, event) => item.activate(event), - onSecondaryClick: (_, event) => item.openMenu(event), - }); diff --git a/modules/wm/ags/modules/wm.js b/modules/wm/ags/modules/wm.js deleted file mode 100644 index 02eee7b..0000000 --- a/modules/wm/ags/modules/wm.js +++ /dev/null @@ -1,96 +0,0 @@ -import Gdk from "gi://Gdk?version=3.0"; - -// const hyprland = await Service.import("hyprland"); -// -// const display = Gdk.Display.get_default(); -// /** @param {number} monitor */ -// const getMonitorName = (monitor) => { -// return display.get_default_screen().get_monitor_plug_name(monitor); -// }; -// -// /** @type {Record} */ -// const urgents = {} -// -// /** @param {{monitor: number, labels: string[]} & import("types/widgets/box").BoxProps} props */ -// export const Tags = ({ monitor, labels, ...props }) => { -// const monName = getMonitorName(monitor); -// // @ts-ignore -// return Widget.Box({ -// ...props, -// children: Array.from({ length: 9 }, (_, i) => i).map((i) => -// Widget.EventBox({ -// child: Widget.Label({ label: labels[i], className: "tags" }), -// attribute: i + 1, -// onPrimaryClickRelease: () => { -// hyprland.message(`dispatch workspace ${i + 1}`); -// }, -// }), -// ), -// setup: (self) => { -// self.hook(hyprland, (_, address) => { -// const client = hyprland.getClient(address); -// if (!client) return; -// const ws = client.workspace.id; -// urgents[ws] = true; -// }, "urgent-window"); -// -// self.hook(hyprland, () => -// self.children.forEach((btn) => { -// const mon = hyprland.monitors.find((x) => x.name === monName); -// const ws = hyprland.workspaces.find((x) => x.id === btn.attribute); -// -// const occupied = (ws?.windows ?? 0) > 0; -// const selected = mon?.activeWorkspace?.id === btn.attribute; -// if (selected) urgents[btn.attribute] = false; -// const urgent = urgents[btn.attribute]; -// -// btn.visible = occupied || selected; -// btn.class_names = [ -// selected ? "accent" : "", -// urgent ? "secondary" : "", -// ]; -// }), -// ); -// }, -// }); -// }; -// -// /** @param {{monitor: number } & import("types/widgets/label").LabelProps} props */ -// export const Layout = ({ monitor, ...props }) => { -// const monName = getMonitorName(monitor); -// return Widget.Label({ -// className: "module", -// ...props, -// }).hook( -// hyprland, -// (self) => { -// const mon = hyprland.monitors.find((x) => x.name === monName); -// const ws = hyprland.workspaces.find( -// (x) => x.id === mon?.activeWorkspace?.id, -// ); -// self.label = ws?.windows ? `[${ws?.windows}]` : ""; -// }, -// "changed", -// ); -// } -// -// /** @param {{monitor: number, fallback?: string} & import("types/widgets/label").LabelProps} props */ -// export const ClientLabel = ({ monitor, fallback = "", ...props }) => { -// const monName = getMonitorName(monitor); -// return Widget.Label({ -// truncate: "end", -// maxWidthChars: 25, -// className: "module", -// ...props, -// }).hook( -// hyprland, -// (self) => { -// const mon = hyprland.monitors.find((x) => x.name === monName); -// const ws = hyprland.workspaces.find( -// (x) => x.id === mon?.activeWorkspace?.id, -// ); -// self.label = ws?.lastwindowtitle || fallback; -// }, -// "changed", -// ); -// }; diff --git a/modules/wm/ags/services/brightness.js b/modules/wm/ags/services/brightness.js deleted file mode 100644 index 65dca83..0000000 --- a/modules/wm/ags/services/brightness.js +++ /dev/null @@ -1,44 +0,0 @@ -const screen = await Utils.execAsync( - "/bin/sh -c 'ls -w1 /sys/class/backlight | head -n 1'", -); -const max = Number(await Utils.execAsync("brightnessctl m")); - -class Brightness extends Service { - static { - Service.register(this, {}, { screen: ["float", "rw"] }); - } - - #screen = 0; - - get screen() { - return this.#screen; - } - - set screen(percent) { - if (percent < 0) percent = 0; - if (percent > 1) percent = 1; - - Utils.execAsync(`brightnessctl s ${percent * 100}% -q`) - .then(() => { - this.#screen = percent; - - this.emit("changed"); - this.notify("screen"); - }) - .catch(print); - } - - constructor() { - super(); - this.#screen = Number(Utils.exec("brightnessctl g")) / max; - - const screenPath = `/sys/class/backlight/${screen}/brightness`; - - Utils.monitorFile(screenPath, async (f) => { - const v = await Utils.readFileAsync(f); - this.#screen = Number(v) / max; - this.changed("screen"); - }); - } -} -export default new Brightness(); diff --git a/modules/wm/ags/style.css b/modules/wm/ags/style.css deleted file mode 100644 index 74921f9..0000000 --- a/modules/wm/ags/style.css +++ /dev/null @@ -1,232 +0,0 @@ -* { - font-family: monospace; - padding: 0; - margin: 0; -} - -button { - all: unset; -} - -/* Colors */ -.transparent { - background-color: rgba(0, 0, 0, 0.6); -} -.bgcont { - background-color: #1E1E2E; - border: 1px solid #11111B; -} -.surface { - background-color: #11111B; - color: #CDD6F4; -} -.accent { - background-color: #94e2d5; - color: #1e1e2e; -} -.accent-rev { - color: #94e2d5; - background-color: #1e1e2e; -} -.secondary { - background-color: #cba6f7; - color: #1e1e2e; -} -.red { - color: #F38BA8; -} -.green { - color: #A6E3A1; -} - -/* Misc */ -.bold { - font-weight: bold; -} -.f16 { - font-size: 16px; -} -.round { - border-radius: 50%; -} -.r20 { - border-radius: 20px; -} -.r100 { - border-radius: 100px; -} -.p10 { - padding: 10px; -} - -separator { - border-radius: 10px; - min-height: 1px; - min-width: 1px; -} - -/* Slider */ -slider { - box-shadow: none; - background-color: transparent; - border: 1px solid transparent; - transition: 200ms; - min-height: 1rem; - border-radius: 100px; -} -trough { - transition: 200ms; - border: 1px; - background-color: #000; - border-radius: 100px; -} -highlight, fill { - background-color: #94e2d5; - border-radius: 100px; - transition: 200ms; -} -mark { - background-color: #F38BA8; - min-width: 1px; - min-height: 3px; -} - -switch { - background-color: #1E1E2E; - border-radius: 100px; -} - -switch slider { - all: unset; - transition: 200ms; - background-color: #94e2d5; - border-radius: 100px; - min-width: 1rem; - min-height: 1rem; -} - -switch image { - all: unset; - color: transparent; -} - -/* Bar */ -.module { - padding: 1px 10px; - margin: 0 4px; -} -.tags { - min-width: 2rem; -} -.qs-item { - padding: 3px; -} - -/* On Screen Display */ -.osd { - border-radius: 20px; - margin-bottom: 150px; - min-width: 175px; - min-height: 30px; -} - -/* Quick settings */ -.qs-container { - min-width: 400px; - padding: 25px; - margin: 10px; - border-radius: 20px; -} -.qs-button { - padding: 0px; - margin: 5px; - border-radius: 20px; - min-width: 225px; - min-height: 75px; - transition: background-color 0.3s ease-in-out; - transition: color 0.3s ease-in-out; -} -.qs-icon { - padding: 12px; -} - -.qs-slider > * { - margin: 5px; -} - -.qs-submenu { - margin-top: 8px; - margin-bottom: 8px; - padding: 12px; - border-radius: 20px; -} -.qs-sub-title { - padding: 14px 20px; - border-radius: 100px; - margin-bottom: 8px; -} -.qs-sub-title > *:first-child { - margin-right: 14px; -} -.qs-sub-content image { - margin-right: 8px; -} - -.qs-sub-content > * { - margin: 6px; -} - -.qs-sub-sub-content > * { - margin: 6px 0px; -} - -.avatar { - border-radius: 50%; - min-width: 70px; - min-height: 70px; - background-size: cover; -} - -.sys-button { - min-width: 40px; - min-height: 40px; - border-radius: 15px; -} - -.mpris-cover-art { - background-size: cover; - background-position: center; - margin: 10px 0; - min-height: 205px; - border-radius: 20px; - transition: all 0.2s; -} -.mpris-cover-art > * { - padding: 20px; -} -.mpris-play { - min-width: 60px; - min-height: 60px; - border-radius: 20px; - padding: 0; - transition: all 0.3s; -} -.mpris-position-slider { - margin: 0 10px; -} -.mpris-position-slider trough { - border-radius: 100px; - background-color: rgba(255, 255, 255, 0.6); - min-height: 4px; -} -.mpris-position-slider highlight, .mpris-position-slider progress { - background-color: #fff; - border-radius: 100px; -} - -.mpris-position-slider slider { - border-radius: 100px; - min-height: 4px; - min-width: 4px; - background-color: #fff; -} diff --git a/modules/wm/ags/tsconfig.json b/modules/wm/ags/tsconfig.json deleted file mode 100644 index 6860767..0000000 --- a/modules/wm/ags/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "ES2022", - "lib": [ - "ES2022" - ], - "allowJs": true, - "checkJs": true, - "strict": true, - "noImplicitAny": false, - "baseUrl": ".", - "typeRoots": [ - "./types" - ], - "skipLibCheck": true - } -} diff --git a/modules/wm/ags/types b/modules/wm/ags/types deleted file mode 120000 index f11a357..0000000 --- a/modules/wm/ags/types +++ /dev/null @@ -1 +0,0 @@ -/nix/store/g5s50dy9za9inrw5zps05s7kh4ypnbpa-ags-1.8.2/share/com.github.Aylur.ags/types \ No newline at end of file diff --git a/modules/wm/home.nix b/modules/wm/home.nix index 5970181..1f3ee63 100644 --- a/modules/wm/home.nix +++ b/modules/wm/home.nix @@ -1,6 +1,7 @@ { pkgs, lib, + noctalia, ... }: let wallpaper = pkgs.writeShellScriptBin "wallpaper" '' @@ -39,9 +40,9 @@ in { imports = [ ./rofi - ./quickshell ./fcitx5.nix ./hyprlock.nix + noctalia.homeModules.default ]; home.packages = [ wallpaper @@ -89,4 +90,127 @@ in { button-layout = ""; }; }; + + programs.noctalia-shell = { + enable = true; + systemd.enable = true; + settings = { + bar = { + capsuleOpacity = 0.5; + showCapsule = false; + outerCorners = false; + widgets = { + left = [ + { + id = "TaskbarGrouped"; + hideUnoccupied = true; + labelMode = "none"; + } + ]; + center = [ + { + id = "NotificationHistory"; + } + ]; + right = [ + { + id = "MediaMini"; + maxWidth = 250; + showArtistFirst = false; + } + { + id = "Spacer"; + } + { + id = "Tray"; + } + { + id = "Battery"; + displayMode = "alwaysShow"; + warningThreshold = 30; + } + { + id = "Volume"; + } + { + id = "Bluetooth"; + } + { + id = "WiFi"; + } + { + id = "Spacer"; + } + { + id = "Clock"; + formatHorizontal = "HH:mm\\nyyyy-MM-dd"; + } + ]; + }; + }; + controlCenter = { + position = "top_center"; + }; + notifications = { + enabled = true; + location = "bar"; + lowUrgencyDuration = 3; + normalUrgencyDuration = 3; + criticalUrgencyDuration = 3; + }; + osd = { + enabled = true; + location = "bottom"; + }; + sessionMenu = { + enableCountdown = true; + position = "top_right"; + powerOptions = [ + { + action = "lock"; + countdownEnabled = false; + enabled = true; + } + { + action = "suspend"; + countdownEnabled = false; + enabled = true; + } + { + action = "hibernate"; + countdownEnabled = true; + enabled = true; + } + { + action = "reboot"; + countdownEnabled = true; + enabled = true; + } + { + action = "logout"; + countdownEnabled = false; + enabled = true; + } + { + action = "shutdown"; + countdownEnabled = true; + enabled = true; + } + ]; + showHeader = true; + }; + settingsVersion = 23; + setupCompleted = true; + general = { + lockOnSuspend = false; + showScreenCorners = false; + }; + colorSchemes = { + predefinedScheme = "Catppuccin"; + }; + wallpaper.enabled = false; + dock.enabled = false; + nightLight.enabled = false; + }; + }; } diff --git a/modules/wm/quickshell/.qmlformat.ini b/modules/wm/quickshell/.qmlformat.ini deleted file mode 100644 index 33bf534..0000000 --- a/modules/wm/quickshell/.qmlformat.ini +++ /dev/null @@ -1 +0,0 @@ -UseTabs=true diff --git a/modules/wm/quickshell/.qmlls.ini b/modules/wm/quickshell/.qmlls.ini deleted file mode 120000 index f6c2fb4..0000000 --- a/modules/wm/quickshell/.qmlls.ini +++ /dev/null @@ -1 +0,0 @@ -/run/user/1000/quickshell/vfs/61f729b565ae1eaaa7b71145d10755fe/.qmlls.ini \ No newline at end of file diff --git a/modules/wm/quickshell/default.nix b/modules/wm/quickshell/default.nix deleted file mode 100644 index 443cdf4..0000000 --- a/modules/wm/quickshell/default.nix +++ /dev/null @@ -1,10 +0,0 @@ -{ - programs.quickshell = { - enable = true; - systemd.enable = true; - configs = { - default = ./.; - }; - activeConfig = "default"; - }; -} diff --git a/modules/wm/quickshell/shell.qml b/modules/wm/quickshell/shell.qml deleted file mode 100644 index 74999d3..0000000 --- a/modules/wm/quickshell/shell.qml +++ /dev/null @@ -1,16 +0,0 @@ -import Quickshell -import QtQuick -import qs.widgets - - -ShellRoot { - Bar {} - - Connections { - function onReloadCompleted() { - Quickshell.inhibitReloadPopup(); - } - - target: Quickshell - } -} diff --git a/modules/wm/quickshell/utils/Settings.qml b/modules/wm/quickshell/utils/Settings.qml deleted file mode 100644 index 3794e37..0000000 --- a/modules/wm/quickshell/utils/Settings.qml +++ /dev/null @@ -1,13 +0,0 @@ -pragma Singleton - -import Quickshell -import QtQuick - -Singleton { - id: root - property QtObject colors - - colors: QtObject { - readonly property color base: "#313244" - } -} diff --git a/modules/wm/quickshell/widgets/Bar.qml b/modules/wm/quickshell/widgets/Bar.qml deleted file mode 100644 index 678324b..0000000 --- a/modules/wm/quickshell/widgets/Bar.qml +++ /dev/null @@ -1,37 +0,0 @@ -import Quickshell -import QtQuick - -import qs.utils - -Scope { - Variants { - model: Quickshell.screens - - delegate: Component { - PanelWindow { - required property var modelData - - screen: modelData - anchors { - top: true - left: true - right: true - } - color: Settings.colors.base - - implicitHeight: 30 - - Text { - anchors.centerIn: parent - - text: Qt.formatDateTime(clock.date, "hh:mm") - } - } - } - } - - SystemClock { - id: clock - precision: SystemClock.Minutes - } -}