diff --git a/flake.lock b/flake.lock index b3da295..05a30e6 100644 --- a/flake.lock +++ b/flake.lock @@ -335,6 +335,24 @@ "type": "github" } }, + "matugen": { + "inputs": { + "nixpkgs": "nixpkgs_3" + }, + "locked": { + "lastModified": 1711657889, + "narHash": "sha256-4VX7Rt+ftEH8nwg59eT7TsvHYUf8/euUmwh/JLc4rLc=", + "owner": "InioX", + "repo": "matugen", + "rev": "566277529dadc2b149a8bd8b9859ea791ecdef26", + "type": "github" + }, + "original": { + "owner": "InioX", + "repo": "matugen", + "type": "github" + } + }, "nix-darwin": { "inputs": { "nixpkgs": [ @@ -389,6 +407,22 @@ } }, "nixpkgs_3": { + "locked": { + "lastModified": 1691186842, + "narHash": "sha256-wxBVCvZUwq+XS4N4t9NqsHV4E64cPVqQ2fdDISpjcw0=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "18036c0be90f4e308ae3ebcab0e14aae0336fe42", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_4": { "locked": { "lastModified": 1712791164, "narHash": "sha256-3sbWO1mbpWsLepZGbWaMovSO7ndZeFqDSdX0hZ9nVyw=", @@ -479,7 +513,8 @@ "anyrun": "anyrun", "home-manager": "home-manager", "hyprland": "hyprland", - "nixpkgs": "nixpkgs_3", + "matugen": "matugen", + "nixpkgs": "nixpkgs_4", "nixvim": "nixvim", "nur": "nur" } diff --git a/flake.nix b/flake.nix index f6c9cb4..534a15f 100644 --- a/flake.nix +++ b/flake.nix @@ -6,6 +6,7 @@ url = "github:nix-community/home-manager"; inputs.nixpkgs.follows = "nixpkgs"; }; + matugen.url = "github:InioX/matugen"; ags.url = "github:Aylur/ags"; nixvim = { url = "github:nix-community/nixvim"; @@ -27,6 +28,7 @@ nixvim, anyrun, hyprland, + matugen, ... }@inputs: let @@ -50,10 +52,12 @@ hyprland = hyprland.packages.${prev.system}.hyprland; xdg-desktop-portal-hyprland = hyprland.packages.${prev.system}.xdg-desktop-portal-hyprland; ags = ags.packages.${prev.system}.default; + matugen = matugen.packages.${prev.system}.default; gbmonctl = prev.callPackage ./overlays/gbmonctl { }; lpc21isp = prev.callPackage ./overlays/lpc21isp { }; darkman = prev.callPackage ./overlays/darkman { }; cura = prev.callPackage ./overlays/cura { }; + asztal = prev.callPackage ./overlays/asztal { }; }) ]; } diff --git a/modules/home-manager/default.nix b/modules/home-manager/default.nix index 69dab9d..ecbdbbe 100644 --- a/modules/home-manager/default.nix +++ b/modules/home-manager/default.nix @@ -14,6 +14,7 @@ inputs.ags.homeManagerModules.default inputs.nixvim.homeManagerModules.nixvim inputs.anyrun.homeManagerModules.default + ./shell/asztal.nix ./clean-home-dir.nix ./programs/neovide.nix # ./default-apps.nix diff --git a/modules/home-manager/desktops/hyprland/ags/.eslintrc.yml b/modules/home-manager/desktops/hyprland/ags/.eslintrc.yml deleted file mode 100644 index 05b56ca..0000000 --- a/modules/home-manager/desktops/hyprland/ags/.eslintrc.yml +++ /dev/null @@ -1,77 +0,0 @@ -env: - es2021: true -extends: eslint:recommended -overrides: [] -parserOptions: - ecmaVersion: latest - sourceType: "module" -rules: - arrow-parens: - - error - - as-needed - comma-dangle: - - error - - always-multiline - comma-spacing: - - error - - before: false - after: true - comma-style: - - error - - last - curly: - - error - - multi-or-nest - - consistent - dot-location: - - error - - property - eol-last: error - indent: - - error - - 4 - - SwitchCase: 1 - keyword-spacing: - - error - - before: true - lines-between-class-members: - - error - - always - - exceptAfterSingleLine: true - padded-blocks: - - error - - never - - allowSingleLineBlocks: false - prefer-const: error - quotes: - - error - - single - - avoidEscape: true - semi: - - error - - always - nonblock-statement-body-position: - - error - - below - no-trailing-spaces: - - error - array-bracket-spacing: - - error - - never - key-spacing: - - error - - beforeColon: false - afterColon: true - object-curly-spacing: - - error - - always - no-useless-escape: - - off -globals: - pkg: readonly - ags: readonly - ARGV: readonly - imports: readonly - print: readonly - console: readonly - logError: readonly diff --git a/modules/home-manager/desktops/hyprland/ags/.stylelintrc.yml b/modules/home-manager/desktops/hyprland/ags/.stylelintrc.yml deleted file mode 100644 index bf75e9a..0000000 --- a/modules/home-manager/desktops/hyprland/ags/.stylelintrc.yml +++ /dev/null @@ -1,14 +0,0 @@ -extends: stylelint-config-standard-scss -ignoreFiles: - - "**/*.js" - - "**/*.ts" -rules: - selector-type-no-unknown: null - declaration-empty-line-before: null - no-descending-specificity: null - selector-pseudo-class-no-unknown: null - color-function-notation: legacy - alpha-value-notation: number - scss/operator-no-unspaced: null - scss/no-global-function-names: null - scss/dollar-variable-empty-line-before: null diff --git a/modules/home-manager/desktops/hyprland/ags/assets/aylur.jpg b/modules/home-manager/desktops/hyprland/ags/assets/aylur.jpg deleted file mode 100644 index 614af5e..0000000 Binary files a/modules/home-manager/desktops/hyprland/ags/assets/aylur.jpg and /dev/null differ diff --git a/modules/home-manager/desktops/hyprland/ags/assets/ivory.png b/modules/home-manager/desktops/hyprland/ags/assets/ivory.png deleted file mode 100644 index 4b1ddf4..0000000 Binary files a/modules/home-manager/desktops/hyprland/ags/assets/ivory.png and /dev/null differ diff --git a/modules/home-manager/desktops/hyprland/ags/assets/kitty.jpeg b/modules/home-manager/desktops/hyprland/ags/assets/kitty.jpeg deleted file mode 100644 index 70ce6b7..0000000 Binary files a/modules/home-manager/desktops/hyprland/ags/assets/kitty.jpeg and /dev/null differ diff --git a/modules/home-manager/desktops/hyprland/ags/assets/kittybl.jpeg b/modules/home-manager/desktops/hyprland/ags/assets/kittybl.jpeg deleted file mode 100644 index 47e50c1..0000000 Binary files a/modules/home-manager/desktops/hyprland/ags/assets/kittybl.jpeg and /dev/null differ diff --git a/modules/home-manager/desktops/hyprland/ags/assets/leaves.jpg b/modules/home-manager/desktops/hyprland/ags/assets/leaves.jpg deleted file mode 100644 index e123e75..0000000 Binary files a/modules/home-manager/desktops/hyprland/ags/assets/leaves.jpg and /dev/null differ diff --git a/modules/home-manager/desktops/hyprland/ags/assets/space.jpg b/modules/home-manager/desktops/hyprland/ags/assets/space.jpg deleted file mode 100644 index 6e47c24..0000000 Binary files a/modules/home-manager/desktops/hyprland/ags/assets/space.jpg and /dev/null differ diff --git a/modules/home-manager/desktops/hyprland/ags/assets/wallpapers/Lakeside/Lakeside_2019_Evening_UHD2.png b/modules/home-manager/desktops/hyprland/ags/assets/wallpapers/Lakeside/Lakeside_2019_Evening_UHD2.png deleted file mode 100644 index e10f06e..0000000 Binary files a/modules/home-manager/desktops/hyprland/ags/assets/wallpapers/Lakeside/Lakeside_2019_Evening_UHD2.png and /dev/null differ diff --git a/modules/home-manager/desktops/hyprland/ags/assets/wallpapers/Lakeside/Lakeside_2019_NoDeer_Evening_UHD2.png b/modules/home-manager/desktops/hyprland/ags/assets/wallpapers/Lakeside/Lakeside_2019_NoDeer_Evening_UHD2.png deleted file mode 100644 index 03714f8..0000000 Binary files a/modules/home-manager/desktops/hyprland/ags/assets/wallpapers/Lakeside/Lakeside_2019_NoDeer_Evening_UHD2.png and /dev/null differ diff --git a/modules/home-manager/desktops/hyprland/ags/assets/wallpapers/Lakeside/Lakeside_2019_NoDeer_UHD2.png b/modules/home-manager/desktops/hyprland/ags/assets/wallpapers/Lakeside/Lakeside_2019_NoDeer_UHD2.png deleted file mode 100644 index 1bcbdea..0000000 Binary files a/modules/home-manager/desktops/hyprland/ags/assets/wallpapers/Lakeside/Lakeside_2019_NoDeer_UHD2.png and /dev/null differ diff --git a/modules/home-manager/desktops/hyprland/ags/assets/wallpapers/Lakeside/Lakeside_2019_Teal_NoDeer_UHD2.png b/modules/home-manager/desktops/hyprland/ags/assets/wallpapers/Lakeside/Lakeside_2019_Teal_NoDeer_UHD2.png deleted file mode 100644 index 2c3c4ac..0000000 Binary files a/modules/home-manager/desktops/hyprland/ags/assets/wallpapers/Lakeside/Lakeside_2019_Teal_NoDeer_UHD2.png and /dev/null differ diff --git a/modules/home-manager/desktops/hyprland/ags/assets/wallpapers/Lakeside/Lakeside_2019_Teal_UHD2.png b/modules/home-manager/desktops/hyprland/ags/assets/wallpapers/Lakeside/Lakeside_2019_Teal_UHD2.png deleted file mode 100644 index 26cda7e..0000000 Binary files a/modules/home-manager/desktops/hyprland/ags/assets/wallpapers/Lakeside/Lakeside_2019_Teal_UHD2.png and /dev/null differ diff --git a/modules/home-manager/desktops/hyprland/ags/assets/wallpapers/Lakeside/Lakeside_2019_UHD2.png b/modules/home-manager/desktops/hyprland/ags/assets/wallpapers/Lakeside/Lakeside_2019_UHD2.png deleted file mode 100644 index e145b40..0000000 Binary files a/modules/home-manager/desktops/hyprland/ags/assets/wallpapers/Lakeside/Lakeside_2019_UHD2.png and /dev/null differ diff --git a/modules/home-manager/desktops/hyprland/ags/assets/wallpapers/Lakeside/lakeside_2019_midnight.png b/modules/home-manager/desktops/hyprland/ags/assets/wallpapers/Lakeside/lakeside_2019_midnight.png deleted file mode 100644 index 45e94a7..0000000 Binary files a/modules/home-manager/desktops/hyprland/ags/assets/wallpapers/Lakeside/lakeside_2019_midnight.png and /dev/null differ diff --git a/modules/home-manager/desktops/hyprland/ags/config.js b/modules/home-manager/desktops/hyprland/ags/config.js deleted file mode 100644 index a9f8f3c..0000000 --- a/modules/home-manager/desktops/hyprland/ags/config.js +++ /dev/null @@ -1,2 +0,0 @@ -import { default as main } from "./js/main.js"; -export default main; diff --git a/modules/home-manager/desktops/hyprland/ags/js/about/about.js b/modules/home-manager/desktops/hyprland/ags/js/about/about.js deleted file mode 100644 index 242fe56..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/about/about.js +++ /dev/null @@ -1,96 +0,0 @@ -import PopupWindow from "../misc/PopupWindow.js"; -import icons from "../icons.js"; - -const pkg = JSON.parse(Utils.readFile(App.configDir + "/package.json")); -const show = JSON.parse( - Utils.readFile(Utils.CACHE_DIR + "/show_about") || "true", -); -const dontShow = () => - Utils.writeFile("false", Utils.CACHE_DIR + "/show_about"); -const avatar = App.configDir + "/assets/aylur.jpg"; - -/** - * @param {Object} o - * @param {string} o.label - * @param {string} o.link - */ -const LinkButton = ({ label, link }) => - Widget.Button({ - on_clicked: () => Utils.execAsync(["xdg-open", link]), - child: Widget.Box({ - children: [ - Widget.Label({ label, hexpand: true, xalign: 0 }), - Widget.Icon(icons.ui.link), - ], - }), - }); - -export default () => - PopupWindow({ - name: "about", - transition: "slide_down", - child: Widget.Box({ - vertical: true, - class_name: "window-content", - children: [ - Widget.Box({ - class_name: "avatar", - hpack: "center", - css: `background-image: url('${avatar}');`, - }), - Widget.Box({ - vertical: true, - class_name: "labels vertical", - children: [ - Widget.Label({ - class_name: "title", - label: pkg.description, - }), - Widget.Label({ - class_name: "author", - label: pkg.author, - }), - Widget.Label({ - class_name: "version", - hpack: "center", - label: pkg.version, - }), - ], - }), - Widget.Box({ - class_name: "buttons", - vertical: true, - vexpand: true, - vpack: "end", - children: [ - LinkButton({ - label: "Support me on Ko-fi", - link: pkg.kofi, - }), - LinkButton({ - label: "Report an Issue", - link: pkg.bugs.url, - }), - ], - }), - Widget.Button({ - class_name: "dont-show", - on_clicked: () => { - dontShow(); - App.toggleWindow("about"); - }, - child: Widget.Box({ - children: [ - Widget.Label("Don't show again"), - Widget.Box({ hexpand: true }), - Widget.Icon(icons.ui.close), - ], - }), - }), - ], - }), - }); - -export function showAbout(force = false) { - if (show || force) App.toggleWindow("about"); -} diff --git a/modules/home-manager/desktops/hyprland/ags/js/applauncher/AppItem.js b/modules/home-manager/desktops/hyprland/ags/js/applauncher/AppItem.js deleted file mode 100644 index 6fd7a34..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/applauncher/AppItem.js +++ /dev/null @@ -1,44 +0,0 @@ -import options from "../options.js"; - -/** @param {import('resource:///com/github/Aylur/ags/service/applications.js').Application} app */ -export default (app) => { - const title = Widget.Label({ - class_name: "title", - label: app.name, - xalign: 0, - vpack: "center", - truncate: "end", - }); - - const description = Widget.Label({ - class_name: "description", - label: app.description || "", - wrap: true, - xalign: 0, - justification: "left", - vpack: "center", - }); - - const icon = Widget.Icon({ - icon: Utils.lookUpIcon(app.icon_name || "") ? app.icon_name || "" : "", - size: options.applauncher.icon_size.bind("value"), - }); - - const textBox = Widget.Box({ - vertical: true, - vpack: "center", - children: app.description ? [title, description] : [title], - }); - - return Widget.Button({ - class_name: "app-item", - attribute: app, - child: Widget.Box({ - children: [icon, textBox], - }), - on_clicked: () => { - App.closeWindow("applauncher"); - app.launch(); - }, - }); -}; diff --git a/modules/home-manager/desktops/hyprland/ags/js/applauncher/Applauncher.js b/modules/home-manager/desktops/hyprland/ags/js/applauncher/Applauncher.js deleted file mode 100644 index f351d82..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/applauncher/Applauncher.js +++ /dev/null @@ -1,90 +0,0 @@ -import Applications from "resource:///com/github/Aylur/ags/service/applications.js"; -import PopupWindow from "../misc/PopupWindow.js"; -import AppItem from "./AppItem.js"; -import icons from "../icons.js"; -import { launchApp } from "../utils.js"; -import options from "../options.js"; - -const WINDOW_NAME = "applauncher"; - -const Applauncher = () => { - const mkItems = () => [ - Widget.Separator({ hexpand: true }), - ...Applications.query("").flatMap((app) => - Widget.Revealer({ - setup: (w) => (w.attribute = { app, revealer: w }), - child: Widget.Box({ - vertical: true, - children: [ - Widget.Separator({ hexpand: true }), - AppItem(app), - Widget.Separator({ hexpand: true }), - ], - }), - }), - ), - Widget.Separator({ hexpand: true }), - ]; - - let items = mkItems(); - - const list = Widget.Box({ - class_name: "app-list", - vertical: true, - children: items, - }); - - const entry = Widget.Entry({ - hexpand: true, - primary_icon_name: icons.apps.search, - - // set some text so on-change works the first time - text: "-", - on_accept: ({ text }) => { - const list = Applications.query(text || ""); - if (list[0]) { - App.toggleWindow(WINDOW_NAME); - launchApp(list[0]); - } - }, - on_change: ({ text }) => - items.map((item) => { - if (item.attribute) { - const { app, revealer } = item.attribute; - revealer.reveal_child = app.match(text); - } - }), - }); - - return Widget.Box({ - vertical: true, - children: [ - entry, - Widget.Scrollable({ - hscroll: "never", - child: list, - }), - ], - setup: (self) => - self.hook(App, (_, win, visible) => { - if (win !== WINDOW_NAME) return; - - entry.text = "-"; - entry.text = ""; - if (visible) { - entry.grab_focus(); - } else { - items = mkItems(); - list.children = items; - } - }), - }); -}; - -export default () => - PopupWindow({ - name: WINDOW_NAME, - transition: "slide_down", - child: Applauncher(), - anchor: options.applauncher.anchor.bind("value"), - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/bar/PanelButton.js b/modules/home-manager/desktops/hyprland/ags/js/bar/PanelButton.js deleted file mode 100644 index 626b28d..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/bar/PanelButton.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * @typedef {Object} PanelButtonProps - * @property {import('types/widgets/button').ButtonProps['child']} content - * @property {string=} window - */ - -/** - * @param {import('types/widgets/button').ButtonProps & PanelButtonProps} o - */ -export default ({ class_name, content, window = "", setup, ...rest }) => - Widget.Button({ - class_name: `panel-button ${class_name}`, - child: Widget.Box({ children: [content] }), - setup: (self) => { - let open = false; - - self.hook(App, (_, win, visible) => { - if (win !== window) return; - - if (open && !visible) { - open = false; - self.toggleClassName("active", false); - } - - if (visible) { - open = true; - self.toggleClassName("active"); - } - }); - - if (setup) setup(self); - }, - ...rest, - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/bar/TopBar.js b/modules/home-manager/desktops/hyprland/ags/js/bar/TopBar.js deleted file mode 100644 index 8f6a081..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/bar/TopBar.js +++ /dev/null @@ -1,113 +0,0 @@ -import SystemTray from "resource:///com/github/Aylur/ags/service/systemtray.js"; -import Notifications from "resource:///com/github/Aylur/ags/service/notifications.js"; -import Mpris from "resource:///com/github/Aylur/ags/service/mpris.js"; -import Battery from "resource:///com/github/Aylur/ags/service/battery.js"; -import OverviewButton from "./buttons/OverviewButton.js"; -import Workspaces from "./buttons/Workspaces.js"; -import FocusedClient from "./buttons/FocusedClient.js"; -import MediaIndicator from "./buttons/MediaIndicator.js"; -import DateButton from "./buttons/DateButton.js"; -import NotificationIndicator from "./buttons/NotificationIndicator.js"; -import SysTray from "./buttons/SysTray.js"; -import ColorPicker from "./buttons/ColorPicker.js"; -import SystemIndicators from "./buttons/SystemIndicators.js"; -import PowerMenu from "./buttons/PowerMenu.js"; -import ScreenRecord from "./buttons/ScreenRecord.js"; -import BatteryBar from "./buttons/BatteryBar.js"; -import SubMenu from "./buttons/SubMenu.js"; -import Recorder from "../services/screenrecord.js"; -// import * as System from './buttons/System.js'; -// import Taskbar from './buttons/Taskbar.js'; -import options from "../options.js"; - -const submenuItems = Variable(1); -SystemTray.connect("changed", () => { - submenuItems.setValue(SystemTray.items.length + 1); -}); - -/** - * @template {import('types/service').default} T - * @param {T=} service - * @param {(service: T) => boolean=} condition - */ -const SeparatorDot = (service, condition) => - Widget.Separator({ - vpack: "center", - setup: (self) => { - const visibility = () => { - if (!options.bar.separators.value) return (self.visible = false); - - self.visible = - condition && service - ? condition(service) - : options.bar.separators.value; - }; - - if (service && condition) self.hook(service, visibility); - - self.on("draw", visibility); - self.bind("visible", options.bar.separators); - }, - }); - -const Start = () => - Widget.Box({ - class_name: "start", - children: [ - OverviewButton(), - SeparatorDot(), - Workspaces(), - SeparatorDot(), - FocusedClient(), - Widget.Box({ hexpand: true }), - NotificationIndicator(), - SeparatorDot(Notifications, (n) => n.notifications.length > 0 || n.dnd), - ], - }); - -const Center = () => - Widget.Box({ - class_name: "center", - children: [DateButton()], - }); - -const End = () => - Widget.Box({ - class_name: "end", - children: [ - SeparatorDot(Mpris, (m) => m.players.length > 0), - MediaIndicator(), - Widget.Box({ hexpand: true }), - - SubMenu({ - items: submenuItems, - children: [SysTray(), ColorPicker()], - }), - - SeparatorDot(), - ScreenRecord(), - SeparatorDot(Recorder, (r) => r.recording), - SystemIndicators(), - SeparatorDot(Battery, (b) => b.available), - SeparatorDot(), - PowerMenu(), - ], - }); - -/** @param {number} monitor */ -export default (monitor) => - Widget.Window({ - name: `bar${monitor}`, - class_name: "transparent", - exclusivity: "exclusive", - monitor, - anchor: options.bar.position - .bind("value") - .transform((pos) => [pos, "left", "right"]), - child: Widget.CenterBox({ - class_name: "panel", - start_widget: Start(), - center_widget: Center(), - end_widget: End(), - }), - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/BatteryBar.js b/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/BatteryBar.js deleted file mode 100644 index 39ecdae..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/BatteryBar.js +++ /dev/null @@ -1,89 +0,0 @@ -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import Battery from "resource:///com/github/Aylur/ags/service/battery.js"; -import icons from "../../icons.js"; -import FontIcon from "../../misc/FontIcon.js"; -import options from "../../options.js"; -import PanelButton from "../PanelButton.js"; - -const Indicator = () => - Widget.Stack({ - children: { - false: Widget.Icon({ icon: Battery.bind("icon_name") }), - true: FontIcon(icons.battery.charging), - }, - visible: options.battery.bar.show_icon.bind("value"), - setup: (self) => - self.hook(Battery, () => { - self.shown = `${Battery.charging || Battery.charged}`; - }), - }); - -const PercentLabel = () => - Widget.Revealer({ - transition: "slide_right", - reveal_child: options.battery.show_percentage.bind("value"), - child: Widget.Label({ - label: Battery.bind("percent").transform((p) => `${p}%`), - }), - }); - -const LevelBar = () => - Widget.LevelBar({ - value: Battery.bind("percent").transform((p) => p / 100), - setup: (self) => - self.hook(options.battery.bar.full, () => { - const full = options.battery.bar.full.value; - self.vpack = full ? "fill" : "center"; - self.hpack = full ? "fill" : "center"; - }), - }); - -const WholeButton = () => - Widget.Overlay({ - class_name: "whole-button", - child: LevelBar(), - pass_through: true, - overlays: [ - Widget.Box({ - hpack: "center", - children: [ - FontIcon({ - icon: icons.battery.charging, - visible: Battery.bind("charging"), - }), - Widget.Box({ - hpack: "center", - vpack: "center", - child: PercentLabel(), - }), - ], - }), - ], - }); - -export default () => - PanelButton({ - class_name: "battery-bar", - on_clicked: () => { - const v = options.battery.show_percentage.value; - options.battery.show_percentage.value = !v; - }, - content: Widget.Box({ - visible: Battery.bind("available"), - children: options.battery.bar.full - .bind("value") - .transform((full) => - full ? [WholeButton()] : [Indicator(), PercentLabel(), LevelBar()], - ), - setup: (self) => - self.hook(Battery, (w) => { - w.toggleClassName("charging", Battery.charging || Battery.charged); - w.toggleClassName( - "medium", - Battery.percent < options.battery.medium.value, - ); - w.toggleClassName("low", Battery.percent < options.battery.low.value); - w.toggleClassName("half", Battery.percent < 48); - }), - }), - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/ColorPicker.js b/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/ColorPicker.js deleted file mode 100644 index b04d01c..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/ColorPicker.js +++ /dev/null @@ -1,26 +0,0 @@ -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import Colors from "../../services/colorpicker.js"; -import PanelButton from "../PanelButton.js"; -import Gdk from "gi://Gdk"; - -export default () => - PanelButton({ - class_name: "color-picker", - content: Widget.Icon("color-select-symbolic"), - tooltip_text: Colors.bind("colors").transform((v) => `${v.length} colors`), - on_clicked: () => Colors.pick(), - on_secondary_click: (btn) => { - if (Colors.colors.length === 0) return; - - Widget.Menu({ - class_name: "colorpicker", - children: Colors.colors.map((color) => - Widget.MenuItem({ - child: Widget.Label(color), - css: `background-color: ${color}`, - on_activate: () => Colors.wlCopy(color), - }), - ), - }).popup_at_widget(btn, Gdk.Gravity.SOUTH, Gdk.Gravity.NORTH, null); - }, - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/DateButton.js b/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/DateButton.js deleted file mode 100644 index 07a0835..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/DateButton.js +++ /dev/null @@ -1,11 +0,0 @@ -import App from "resource:///com/github/Aylur/ags/app.js"; -import Clock from "../../misc/Clock.js"; -import PanelButton from "../PanelButton.js"; - -export default ({ format = "%R - %x" } = {}) => - PanelButton({ - class_name: "dashboard panel-button", - on_clicked: () => App.toggleWindow("dashboard"), - window: "dashboard", - content: Clock({ format }), - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/FocusedClient.js b/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/FocusedClient.js deleted file mode 100644 index 86d9e30..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/FocusedClient.js +++ /dev/null @@ -1,44 +0,0 @@ -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import Hyprland from "resource:///com/github/Aylur/ags/service/hyprland.js"; -import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; -import PanelButton from "../PanelButton.js"; -import options from "../../options.js"; -import { substitute } from "../../utils.js"; - -export const ClientLabel = () => - Widget.Label({ - label: Hyprland.active.client.bind("class").transform((c) => { - const { titles } = options.substitutions; - return substitute(titles, c); - }), - }); - -export const ClientIcon = () => - Widget.Icon({ - setup: (self) => - self.hook(Hyprland.active.client, () => { - const { icons } = options.substitutions; - const { client } = Hyprland.active; - - const classIcon = substitute(icons, client.class) + "-symbolic"; - const titleIcon = substitute(icons, client.class) + "-symbolic"; - - const hasTitleIcon = Utils.lookUpIcon(titleIcon); - const hasClassIcon = Utils.lookUpIcon(classIcon); - - if (hasClassIcon) self.icon = classIcon; - - if (hasTitleIcon) self.icon = titleIcon; - - self.visible = !!(hasTitleIcon || hasClassIcon); - }), - }); - -export default () => - PanelButton({ - class_name: "focused-client", - content: Widget.Box({ - tooltip_text: Hyprland.active.bind("client").transform((c) => c.title), - children: [ClientIcon(), ClientLabel()], - }), - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/MediaIndicator.js b/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/MediaIndicator.js deleted file mode 100644 index 2912b04..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/MediaIndicator.js +++ /dev/null @@ -1,74 +0,0 @@ -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import Mpris from "resource:///com/github/Aylur/ags/service/mpris.js"; -import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; -import HoverRevealer from "../../misc/HoverRevealer.js"; -import * as mpris from "../../misc/mpris.js"; -import options from "../../options.js"; - -export const getPlayer = (name = options.mpris.preferred.value) => - Mpris.getPlayer(name) || Mpris.players[0] || null; - -/** - * @param {Object} o - * @param {import('types/service/mpris').MprisPlayer} o.player - * @param {import('../../misc/HoverRevealer').HoverRevealProps['direction']=} o.direction - */ -const Indicator = ({ player, direction = "right" }) => - HoverRevealer({ - class_name: `media panel-button ${player.name}`, - direction, - on_primary_click: () => player.playPause(), - on_scroll_up: () => player.next(), - on_scroll_down: () => player.previous(), - on_secondary_click: () => player.playPause(), - indicator: mpris.PlayerIcon(player), - child: Widget.Label({ - vexpand: true, - truncate: "end", - max_width_chars: 40, - label: player - .bind("track_title") - .transform( - () => `${player.track_artists.join(", ")} - ${player.track_title}`, - ), - }), - setupRevealer: (self) => { - let current = ""; - self.hook(player, () => { - if (current === player.track_title) return; - - current = player.track_title; - self.reveal_child = true; - Utils.timeout(3000, () => { - self.reveal_child = false; - }); - }); - }, - }); - -/** - * @param {Object} o - * @param {import('../../misc/HoverRevealer').HoverRevealProps['direction']=} o.direction - */ -export default ({ direction = "right" } = {}) => { - let current = null; - - const update = (box) => { - const player = getPlayer(); - box.visible = !!player; - - if (!player) { - current = null; - return; - } - - if (current === player) return; - - current = player; - box.children = [Indicator({ player, direction })]; - }; - - return Widget.Box() - .hook(options.mpris.preferred, update) - .hook(Mpris, update, "notify::players"); -}; diff --git a/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/NotificationIndicator.js b/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/NotificationIndicator.js deleted file mode 100644 index d8ee697..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/NotificationIndicator.js +++ /dev/null @@ -1,50 +0,0 @@ -import App from "resource:///com/github/Aylur/ags/app.js"; -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import Notifications from "resource:///com/github/Aylur/ags/service/notifications.js"; -import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; -import icons from "../../icons.js"; -import HoverRevealer from "../../misc/HoverRevealer.js"; - -/** - * @param {Object} o - * @param {import('../../misc/HoverRevealer').HoverRevealProps['direction']=} o.direction - */ -export default ({ direction = "left" } = {}) => - HoverRevealer({ - class_name: "notifications panel-button", - setupEventBox: (box) => - box - .on("button-press-event", () => App.openWindow("dashboard")) - .hook( - Notifications, - () => - (box.visible = - Notifications.notifications.length > 0 || Notifications.dnd), - ), - - setupRevealer: (self) => - self.hook(Notifications, () => { - let title = ""; - const summary = Notifications.notifications[0]?.summary; - if (title === summary) return; - - title = summary; - self.reveal_child = true; - Utils.timeout(3000, () => { - self.reveal_child = false; - }); - }), - direction, - indicator: Widget.Icon({ - icon: Notifications.bind("dnd").transform( - (dnd) => icons.notifications[dnd ? "silent" : "noisy"], - ), - }), - child: Widget.Label({ - truncate: "end", - max_width_chars: 40, - label: Notifications.bind("notifications").transform( - (n) => n.reverse()[0]?.summary || "", - ), - }), - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/OverviewButton.js b/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/OverviewButton.js deleted file mode 100644 index 9d275cb..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/OverviewButton.js +++ /dev/null @@ -1,17 +0,0 @@ -import App from "resource:///com/github/Aylur/ags/app.js"; -import PanelButton from "../PanelButton.js"; -import FontIcon from "../../misc/FontIcon.js"; -import { distroIcon } from "../../variables.js"; -import options from "../../options.js"; - -export default () => - PanelButton({ - class_name: "overview", - window: "overview", - on_clicked: () => App.toggleWindow("overview"), - content: FontIcon({ - label: options.bar.icon.bind("value").transform((v) => { - return v === "distro-icon" ? distroIcon : v; - }), - }), - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/PowerMenu.js b/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/PowerMenu.js deleted file mode 100644 index dc7b9cc..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/PowerMenu.js +++ /dev/null @@ -1,11 +0,0 @@ -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import App from "resource:///com/github/Aylur/ags/app.js"; -import icons from "../../icons.js"; -import PanelButton from "../PanelButton.js"; - -export default () => - PanelButton({ - class_name: "powermenu", - content: Widget.Icon(icons.powermenu.shutdown), - on_clicked: () => App.openWindow("powermenu"), - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/ScreenRecord.js b/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/ScreenRecord.js deleted file mode 100644 index 4e2d515..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/ScreenRecord.js +++ /dev/null @@ -1,23 +0,0 @@ -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import PanelButton from "../PanelButton.js"; -import Recorder from "../../services/screenrecord.js"; -import icons from "../../icons.js"; - -export default () => - PanelButton({ - class_name: "recorder", - on_clicked: () => Recorder.stop(), - visible: Recorder.bind("recording"), - content: Widget.Box({ - children: [ - Widget.Icon(icons.recorder.recording), - Widget.Label({ - label: Recorder.bind("timer").transform((time) => { - const sec = time % 60; - const min = Math.floor(time / 60); - return `${min}:${sec < 10 ? "0" + sec : sec}`; - }), - }), - ], - }), - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/SubMenu.js b/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/SubMenu.js deleted file mode 100644 index 163fcf3..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/SubMenu.js +++ /dev/null @@ -1,65 +0,0 @@ -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; -import Variable from "resource:///com/github/Aylur/ags/variable.js"; -import icons from "../../icons.js"; -import options from "../../options.js"; - -/** - * @param {import('types/widgets/revealer').default} revealer - * @param {'left' | 'right' | 'up' | 'down'} direction - * @param {import('types/variable').Variable} items - */ -const Arrow = (revealer, direction, items) => { - let deg = 0; - - const icon = Widget.Icon({ - icon: icons.ui.arrow[direction], - }); - - const animate = () => { - const t = options.transition.value / 20; - const step = revealer.reveal_child ? 10 : -10; - for (let i = 0; i < 18; ++i) { - Utils.timeout(t * i, () => { - deg += step; - icon.setCss(`-gtk-icon-transform: rotate(${deg}deg);`); - }); - } - }; - - return Widget.Button({ - class_name: "panel-button sub-menu", - tooltip_text: items.bind().transform((v) => `${v} Items`), - on_clicked: () => { - animate(); - revealer.reveal_child = !revealer.reveal_child; - }, - child: icon, - }); -}; - -/** - * @param {Object} o - * @param {import('types/widgets/box').default['children']} o.children - * @param {'left' | 'right' | 'up' | 'down'=} o.direction - * @param {import('types/variable').Variable} o.items - */ -export default ({ children, direction = "left", items = Variable(0) }) => { - const posStart = direction === "up" || direction === "left"; - const posEnd = direction === "down" || direction === "right"; - const revealer = Widget.Revealer({ - transition: `slide_${direction}`, - child: Widget.Box({ - children, - }), - }); - - return Widget.Box({ - vertical: direction === "up" || direction === "down", - children: [ - posStart && revealer, - Arrow(revealer, direction, items), - posEnd && revealer, - ], - }); -}; diff --git a/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/SysTray.js b/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/SysTray.js deleted file mode 100644 index ee248c4..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/SysTray.js +++ /dev/null @@ -1,44 +0,0 @@ -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import SystemTray from "resource:///com/github/Aylur/ags/service/systemtray.js"; -import PanelButton from "../PanelButton.js"; -import Gdk from "gi://Gdk"; - -/** @param {import('types/service/systemtray').TrayItem} item */ -const SysTrayItem = (item) => - PanelButton({ - class_name: "tray-item", - content: Widget.Icon({ icon: item.bind("icon") }), - tooltip_markup: item.bind("tooltip_markup"), - setup: (self) => { - const id = item.menu?.connect("popped-up", (menu) => { - self.toggleClassName("active"); - menu.connect("notify::visible", (menu) => { - self.toggleClassName("active", menu.visible); - }); - menu.disconnect(id); - }); - - if (id) self.connect("destroy", () => item.menu?.disconnect(id)); - }, - - // @ts-expect-error popup_at_widget missing from types? - on_primary_click: (btn) => - item.menu?.popup_at_widget( - btn, - Gdk.Gravity.SOUTH, - Gdk.Gravity.NORTH, - null, - ), - - // @ts-expect-error popup_at_widget missing from types? - on_secondary_click: (btn) => - item.menu?.popup_at_widget( - btn, - Gdk.Gravity.SOUTH, - Gdk.Gravity.NORTH, - null, - ), - }); - -export default () => - Widget.Box().bind("children", SystemTray, "items", (i) => i.map(SysTrayItem)); diff --git a/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/System.js b/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/System.js deleted file mode 100644 index f8a6825..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/System.js +++ /dev/null @@ -1,50 +0,0 @@ -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import PanelButton from "../PanelButton.js"; -import * as variables from "../../variables.js"; -import icons from "../../icons.js"; - -/** @param {'cpu' | 'ram'} type */ -const System = (type) => { - const icon = Widget.Icon({ - class_name: "icon", - icon: icons.system[type], - }); - - const progress = Widget.Box({ - class_name: "progress", - child: Widget.CircularProgress({ - value: variables[type].bind(), - }), - }); - - const revealer = Widget.Revealer({ - transition: "slide_right", - child: Widget.Label({ - label: variables[type].bind("value").transform((v) => { - return ` ${type}: ${Math.round(v * 100)}%`; - }), - }), - }); - - return PanelButton({ - class_name: `system ${type}`, - on_clicked: () => (revealer.reveal_child = !revealer.reveal_child), - content: Widget.EventBox({ - on_hover: () => (revealer.reveal_child = true), - on_hover_lost: () => (revealer.reveal_child = false), - child: Widget.Box({ - children: [ - icon, - Widget.Box({ - class_name: "revealer", - child: revealer, - }), - progress, - ], - }), - }), - }); -}; - -export const CPU = () => System("cpu"); -export const RAM = () => System("ram"); diff --git a/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/SystemIndicators.js b/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/SystemIndicators.js deleted file mode 100644 index 2ba0387..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/SystemIndicators.js +++ /dev/null @@ -1,134 +0,0 @@ -import App from "resource:///com/github/Aylur/ags/app.js"; -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import Notifications from "resource:///com/github/Aylur/ags/service/notifications.js"; -import Bluetooth from "resource:///com/github/Aylur/ags/service/bluetooth.js"; -import Audio from "resource:///com/github/Aylur/ags/service/audio.js"; -import Network from "resource:///com/github/Aylur/ags/service/network.js"; -import HoverRevealer from "../../misc/HoverRevealer.js"; -import PanelButton from "../PanelButton.js"; -import Asusctl from "../../services/asusctl.js"; -import Indicator from "../../services/onScreenIndicator.js"; -import icons from "../../icons.js"; -import FontIcon from "../../misc/FontIcon.js"; - -const ProfileIndicator = () => - Widget.Icon() - .bind("visible", Asusctl, "profile", (p) => p !== "Balanced") - .bind("icon", Asusctl, "profile", (i) => icons.asusctl.profile[i]); - -const ModeIndicator = () => - FontIcon() - .bind("visible", Asusctl, "mode", (m) => m !== "Hybrid") - .bind("icon", Asusctl, "mode", (i) => icons.asusctl.mode[i]); - -const MicrophoneIndicator = () => - Widget.Icon().hook( - Audio, - (icon) => { - if (!Audio.microphone) return; - - const { muted, low, medium, high } = icons.audio.mic; - - /** @type {Array<[number, string]>} */ - const cons = [ - [67, high], - [34, medium], - [1, low], - [0, muted], - ]; - icon.icon = - cons.find(([n]) => n <= Audio.microphone.volume * 100)?.[1] || ""; - - icon.visible = Audio.recorders.length > 0 || Audio.microphone.is_muted; - }, - "microphone-changed", - ); - -const DNDIndicator = () => - Widget.Icon({ - visible: Notifications.bind("dnd"), - icon: icons.notifications.silent, - }); - -const BluetoothDevicesIndicator = () => - Widget.Box().hook( - Bluetooth, - (box) => { - box.children = Bluetooth.connectedDevices.map(({ iconName, name }) => - HoverRevealer({ - indicator: Widget.Icon(iconName + "-symbolic"), - child: Widget.Label(name), - }), - ); - - box.visible = Bluetooth.connectedDevices.length > 0; - }, - "notify::connected-devices", - ); - -const BluetoothIndicator = () => - Widget.Icon({ - class_name: "bluetooth", - icon: icons.bluetooth.enabled, - visible: Bluetooth.bind("enabled"), - }); - -const NetworkIndicator = () => - Widget.Icon().hook(Network, (self) => { - const icon = Network[Network.primary || "wifi"]?.iconName; - self.icon = icon || ""; - self.visible = !!icon; - }); - -const AudioIndicator = () => - Widget.Icon().hook( - Audio, - (self) => { - if (!Audio.speaker) return; - - const { muted, low, medium, high, overamplified } = icons.audio.volume; - if (Audio.speaker.is_muted) return (self.icon = muted); - - /** @type {Array<[number, string]>} */ - const cons = [ - [101, overamplified], - [67, high], - [34, medium], - [1, low], - [0, muted], - ]; - self.icon = - cons.find(([n]) => n <= Audio.speaker.volume * 100)?.[1] || ""; - }, - "speaker-changed", - ); - -export default () => - PanelButton({ - class_name: "quicksettings panel-button", - on_clicked: () => App.toggleWindow("quicksettings"), - setup: (self) => - self.hook(App, (_, win, visible) => { - self.toggleClassName("active", win === "quicksettings" && visible); - }), - on_scroll_up: () => { - Audio.speaker.volume += 0.02; - Indicator.speaker(); - }, - on_scroll_down: () => { - Audio.speaker.volume -= 0.02; - Indicator.speaker(); - }, - content: Widget.Box({ - children: [ - Asusctl?.available && ProfileIndicator(), - Asusctl?.available && ModeIndicator(), - DNDIndicator(), - BluetoothDevicesIndicator(), - BluetoothIndicator(), - NetworkIndicator(), - AudioIndicator(), - MicrophoneIndicator(), - ], - }), - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/Taskbar.js b/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/Taskbar.js deleted file mode 100644 index 27ab730..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/Taskbar.js +++ /dev/null @@ -1,31 +0,0 @@ -import Hyprland from "resource:///com/github/Aylur/ags/service/hyprland.js"; -import Applications from "resource:///com/github/Aylur/ags/service/applications.js"; -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import PanelButton from "../PanelButton.js"; -import { launchApp } from "../../utils.js"; -import icons from "../../icons.js"; - -const focus = ({ address }) => - Hyprland.sendMessage(`dispatch focuswindow address:${address}`); - -/** @param {import('types/widgets/box').default} box */ -const setChildren = (box) => - (box.children = Hyprland.clients.map((client) => { - if (Hyprland.active.workspace.id !== client.workspace.id) return; - - for (const app of Applications.list) { - if (client.class && app.match(client.class)) { - return PanelButton({ - content: Widget.Icon(app.icon_name || icons.fallback.executable), - tooltip_text: app.name, - on_primary_click: () => focus(client), - on_middle_click: () => launchApp(app), - }); - } - } - })); - -export default () => - Widget.Box() - .hook(Hyprland, setChildren, "notify::clients") - .hook(Hyprland, setChildren, "notify::active"); diff --git a/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/Workspaces.js b/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/Workspaces.js deleted file mode 100644 index 3400b0d..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/bar/buttons/Workspaces.js +++ /dev/null @@ -1,58 +0,0 @@ -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import Hyprland from "resource:///com/github/Aylur/ags/service/hyprland.js"; -import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; -import options from "../../options.js"; -import { range } from "../../utils.js"; - -/** @param {any} arg */ -const dispatch = (arg) => Utils.execAsync(`hyprctl dispatch workspace ${arg}`); - -const Workspaces = () => { - const ws = options.workspaces.value; - return Widget.Box({ - children: range(ws || 20).map((i) => - Widget.Button({ - attribute: i, - on_clicked: () => dispatch(i), - child: Widget.Label({ - label: `${i}`, - class_name: "indicator", - vpack: "center", - }), - setup: (self) => - self.hook(Hyprland, () => { - self.toggleClassName("active", Hyprland.active.workspace.id === i); - self.toggleClassName( - "occupied", - (Hyprland.getWorkspace(i)?.windows || 0) > 0, - ); - }), - }), - ), - setup: (box) => { - if (ws === 0) { - box.hook(Hyprland.active.workspace, () => - box.children.map((btn) => { - btn.visible = Hyprland.workspaces.some( - (ws) => ws.id === btn.attribute, - ); - }), - ); - } - }, - }); -}; - -export default () => - Widget.EventBox({ - class_name: "workspaces panel-button", - child: Widget.Box({ - // its nested like this to keep it consistent with other PanelButton widgets - child: Widget.EventBox({ - on_scroll_up: () => dispatch("m+1"), - on_scroll_down: () => dispatch("m-1"), - class_name: "eventbox", - child: options.workspaces.bind("value").transform(Workspaces), - }), - }), - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/dashboard/Dashboard.js b/modules/home-manager/desktops/hyprland/ags/js/dashboard/Dashboard.js deleted file mode 100644 index faad0c6..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/dashboard/Dashboard.js +++ /dev/null @@ -1,26 +0,0 @@ -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import DateColumn from "./DateColumn.js"; -import NotificationColumn from "./NotificationColumn.js"; -import PopupWindow from "../misc/PopupWindow.js"; -import options from "../options.js"; - -export default () => - PopupWindow({ - name: "dashboard", - setup: (self) => - self.hook(options.bar.position, () => { - self.anchor = [options.bar.position.value]; - if (options.bar.position.value === "top") - self.transition = "slide_down"; - - if (options.bar.position.value === "bottom") - self.transition = "slide_up"; - }), - child: Widget.Box({ - children: [ - NotificationColumn(), - Widget.Separator({ orientation: 1 }), - DateColumn(), - ], - }), - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/dashboard/DateColumn.js b/modules/home-manager/desktops/hyprland/ags/js/dashboard/DateColumn.js deleted file mode 100644 index 809bd93..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/dashboard/DateColumn.js +++ /dev/null @@ -1,63 +0,0 @@ -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import icons from "../icons.js"; -import Clock from "../misc/Clock.js"; -import * as vars from "../variables.js"; -import options from "../options.js"; - -/** - * @param {'cpu' | 'ram' | 'temp'} type - * @param {string} title - * @param {string} unit - */ -const SysProgress = (type, title, unit) => - Widget.Box({ - class_name: `circular-progress-box ${type}`, - hexpand: true, - tooltip_text: vars[type] - .bind("value") - .transform((v) => `${title}: ${Math.floor(v * 100)}${unit}`), - child: Widget.CircularProgress({ - hexpand: true, - class_name: `circular-progress ${type}`, - child: Widget.Icon(icons.system[type]), - start_at: 0.75, - value: vars[type].bind(), - rounded: options.radii.bind("value").transform((v) => v > 0), - }), - }); - -export default () => - Widget.Box({ - vertical: true, - class_name: "datemenu vertical", - children: [ - Widget.Box({ - class_name: "clock-box", - vertical: true, - children: [ - Clock({ format: "%H:%M" }), - Widget.Label({ - class_name: "uptime", - label: vars.uptime.bind("value").transform((t) => `uptime: ${t}`), - }), - ], - }), - Widget.Box({ - class_name: "calendar", - children: [ - Widget.Calendar({ - hexpand: true, - hpack: "center", - }), - ], - }), - Widget.Box({ - class_name: "system-info horizontal", - children: [ - SysProgress("cpu", "Cpu", "%"), - SysProgress("ram", "Ram", "%"), - SysProgress("temp", "Temperature", "°"), - ], - }), - ], - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/dashboard/NotificationColumn.js b/modules/home-manager/desktops/hyprland/ags/js/dashboard/NotificationColumn.js deleted file mode 100644 index 15a1af4..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/dashboard/NotificationColumn.js +++ /dev/null @@ -1,81 +0,0 @@ -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import Notifications from "resource:///com/github/Aylur/ags/service/notifications.js"; -import icons from "../icons.js"; -import Notification from "../misc/Notification.js"; -import { timeout } from "resource:///com/github/Aylur/ags/utils.js"; - -const ClearButton = () => - Widget.Button({ - on_clicked: () => { - const list = Array.from(Notifications.notifications); - for (let i = 0; i < list.length; i++) - timeout(50 * i, () => list[i]?.close()); - }, - sensitive: Notifications.bind("notifications").transform( - (n) => n.length > 0, - ), - child: Widget.Box({ - children: [ - Widget.Label("Clear "), - Widget.Icon({ - icon: Notifications.bind("notifications").transform( - (n) => icons.trash[n.length > 0 ? "full" : "empty"], - ), - }), - ], - }), - }); - -const Header = () => - Widget.Box({ - class_name: "header", - children: [ - Widget.Label({ label: "Notifications", hexpand: true, xalign: 0 }), - ClearButton(), - ], - }); - -const NotificationList = () => - Widget.Box({ - vertical: true, - vexpand: true, - children: Notifications.bind("notifications").transform((n) => - n.reverse().map(Notification), - ), - visible: Notifications.bind("notifications").transform((n) => n.length > 0), - }); - -const Placeholder = () => - Widget.Box({ - class_name: "placeholder", - vertical: true, - vpack: "center", - hpack: "center", - vexpand: true, - hexpand: true, - visible: Notifications.bind("notifications").transform( - (n) => n.length === 0, - ), - children: [ - Widget.Icon(icons.notifications.silent), - Widget.Label("Your inbox is empty"), - ], - }); - -export default () => - Widget.Box({ - class_name: "notifications", - vertical: true, - children: [ - Header(), - Widget.Scrollable({ - vexpand: true, - class_name: "notification-scrollable", - child: Widget.Box({ - class_name: "notification-list", - vertical: true, - children: [NotificationList(), Placeholder()], - }), - }), - ], - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/desktop/Desktop.js b/modules/home-manager/desktops/hyprland/ags/js/desktop/Desktop.js deleted file mode 100644 index 161ae82..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/desktop/Desktop.js +++ /dev/null @@ -1,69 +0,0 @@ -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import Clock from "../misc/Clock.js"; -import DesktopMenu from "./DesktopMenu.js"; -import options from "../options.js"; - -const DesktopClock = () => - Widget.Box({ - class_name: "clock-box-shadow", - child: Widget.CenterBox({ - class_name: "clock-box", - start_widget: Clock({ - class_name: "clock", - hpack: "center", - format: "%H", - }), - center_widget: Widget.Box({ - class_name: "separator-box", - vertical: true, - hexpand: true, - hpack: "center", - children: [ - Widget.Separator({ vpack: "center", vexpand: true }), - Widget.Separator({ vpack: "center", vexpand: true }), - ], - }), - end_widget: Clock({ - class_name: "clock", - hpack: "center", - format: "%M", - }), - }), - }); - -const Desktop = () => - Widget.EventBox({ - on_secondary_click: (_, event) => DesktopMenu().popup_at_pointer(event), - child: Widget.Box({ - vertical: true, - vexpand: true, - hexpand: true, - visible: options.desktop.clock.enable.bind("value"), - setup: (self) => - self.hook(options.desktop.clock.position, () => { - const [hpack = "center", vpack = "center", offset = 64] = - options.desktop.clock.position.value.split(" ") || []; - - // @ts-expect-error - self.hpack = hpack; - self.vpack = vpack; - self.setCss(`margin: ${Number(offset)}px;`); - }), - children: [ - DesktopClock(), - Clock({ format: "%B %e. %A", class_name: "date" }), - ], - }), - }); - -/** @param {number} monitor */ -export default (monitor) => - Widget.Window({ - monitor, - keymode: "on-demand", - name: `desktop${monitor}`, - layer: "background", - class_name: "desktop", - anchor: ["top", "bottom", "left", "right"], - child: Desktop(), - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/desktop/DesktopMenu.js b/modules/home-manager/desktops/hyprland/ags/js/desktop/DesktopMenu.js deleted file mode 100644 index e77b919..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/desktop/DesktopMenu.js +++ /dev/null @@ -1,66 +0,0 @@ -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import App from "resource:///com/github/Aylur/ags/app.js"; -import PowerMenu from "../services/powermenu.js"; -import icons from "../icons.js"; -import Gtk from "gi://Gtk"; -import { openSettings } from "../settings/theme.js"; - -/** - * @param {string} label - * @param {string} icon - * @param {import('types/widgets/menu').MenuItemProps['on_activate']} on_activate - */ -const Item = (label, icon, on_activate) => - Widget.MenuItem({ - on_activate, - child: Widget.Box({ - children: [ - Widget.Icon(icon), - Widget.Label({ - label, - hexpand: true, - xalign: 0, - }), - ], - }), - }); - -export default () => - Widget.Menu({ - class_name: "desktop-menu", - children: [ - Widget.MenuItem({ - child: Widget.Box({ - children: [ - Widget.Icon(icons.powermenu.shutdown), - Widget.Label({ - label: "System", - hexpand: true, - xalign: 0, - }), - ], - }), - submenu: Widget.Menu({ - children: [ - Item("Shutdown", icons.powermenu.shutdown, () => - PowerMenu.action("shutdown"), - ), - Item("Log Out", icons.powermenu.logout, () => - PowerMenu.action("logout"), - ), - Item("Reboot", icons.powermenu.reboot, () => - PowerMenu.action("reboot"), - ), - Item("Sleep", icons.powermenu.sleep, () => - PowerMenu.action("reboot"), - ), - ], - }), - }), - Item("Applications", icons.apps.apps, () => - App.openWindow("applauncher"), - ), - new Gtk.SeparatorMenuItem(), - Item("Settings", icons.ui.settings, openSettings), - ], - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/dock/Dock.js b/modules/home-manager/desktops/hyprland/ags/js/dock/Dock.js deleted file mode 100644 index c660ce4..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/dock/Dock.js +++ /dev/null @@ -1,142 +0,0 @@ -import App from "resource:///com/github/Aylur/ags/app.js"; -import Hyprland from "resource:///com/github/Aylur/ags/service/hyprland.js"; -import Applications from "resource:///com/github/Aylur/ags/service/applications.js"; -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import icons from "../icons.js"; -import options from "../options.js"; -import { launchApp, range } from "../utils.js"; - -const focus = ({ address }) => - Hyprland.sendMessage(`dispatch focuswindow address:${address}`); - -/** @param {import('types/widgets/button').ButtonProps & { icon: string, pinned?: boolean }} o */ -const AppButton = ({ icon, pinned = false, ...rest }) => { - const indicators = Widget.Box({ - vpack: "end", - hpack: "center", - children: range(5, 0).map(() => - Widget.Box({ - class_name: "indicator", - visible: false, - }), - ), - }); - - return Widget.Button({ - ...rest, - attribute: indicators, - child: Widget.Box({ - class_name: "box", - child: Widget.Overlay({ - child: Widget.Icon({ - icon, - size: options.desktop.dock.icon_size.bind("value"), - }), - pass_through: true, - overlays: pinned ? [indicators] : [], - }), - }), - }); -}; - -const Taskbar = () => - Widget.Box({ - children: Hyprland.bind("clients").transform((c) => - c.map((client) => { - for (const appName of options.desktop.dock.pinned_apps.value) { - if (client.class.toLowerCase().includes(appName.toLowerCase())) - return null; - } - for (const app of Applications.list) { - if ( - (client.title && app.match(client.title)) || - (client.class && app.match(client.class)) - ) { - return AppButton({ - icon: app.icon_name || "", - tooltip_text: app.name, - on_primary_click: () => focus(client), - on_middle_click: () => launchApp(app), - }); - } - } - }), - ), - }); - -const PinnedApps = () => - Widget.Box({ - class_name: "pins", - homogeneous: true, - children: options.desktop.dock.pinned_apps.bind("value").transform((v) => - v - .map((term) => ({ app: Applications.query(term)?.[0], term })) - .filter(({ app }) => app) - .map(({ app, term }) => - AppButton({ - pinned: true, - icon: app.icon_name || "", - on_primary_click: () => { - for (const client of Hyprland.clients) { - if (client.class.toLowerCase().includes(term)) - return focus(client); - } - - launchApp(app); - }, - on_middle_click: () => launchApp(app), - tooltip_text: app.name, - setup: (button) => - button.hook(Hyprland, () => { - const running = Hyprland.clients.filter((client) => - client.class.toLowerCase().includes(term), - ); - - const focused = running.find( - (client) => client.address === Hyprland.active.client.address, - ); - - const index = running.findIndex((c) => c === focused); - - for (let i = 0; i < 5; ++i) { - const indicator = button.attribute.children[i]; - indicator.visible = i < running.length; - indicator.toggleClassName("focused", i === index); - } - - button.set_tooltip_text( - running.length === 1 ? running[0].title : app.name, - ); - }), - }), - ), - ), - }); - -export default () => { - const pinnedapps = PinnedApps(); - const taskbar = Taskbar(); - const applauncher = AppButton({ - class_name: "launcher nonrunning", - icon: icons.apps.apps, - tooltip_text: "Applications", - on_clicked: () => App.toggleWindow("applauncher"), - }); - const separator = Widget.Separator({ - vpack: "center", - hpack: "center", - orientation: 1, - setup: (self) => - self.hook( - taskbar, - () => { - self.visible = taskbar.children.length > 0; - }, - "notify::children", - ), - }); - return Widget.Box({ - class_name: "dock", - children: [applauncher, pinnedapps, separator, taskbar], - }); -}; diff --git a/modules/home-manager/desktops/hyprland/ags/js/dock/FloatingDock.js b/modules/home-manager/desktops/hyprland/ags/js/dock/FloatingDock.js deleted file mode 100644 index e6728d1..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/dock/FloatingDock.js +++ /dev/null @@ -1,44 +0,0 @@ -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import Hyprland from "resource:///com/github/Aylur/ags/service/hyprland.js"; -import Dock from "./Dock.js"; -import options from "../options.js"; - -/** @param {number} monitor */ -export default (monitor) => { - const revealer = Widget.Revealer({ - transition: "slide_up", - child: Dock(), - setup: (self) => { - const update = () => { - const ws = Hyprland.getWorkspace(Hyprland.active.workspace.id); - if (Hyprland.getMonitor(monitor)?.name === ws?.monitor) - self.reveal_child = ws?.windows === 0; - }; - self - .hook(Hyprland, update, "client-added") - .hook(Hyprland, update, "client-removed") - .hook(Hyprland.active.workspace, update); - }, - }); - - return Widget.Window({ - monitor, - name: `dock${monitor}`, - class_name: "floating-dock", - anchor: ["bottom"], - child: Widget.Box({ - children: [ - revealer, - Widget.Box({ - class_name: "padding", - css: "padding: 2px;", - }), - ], - }), - setup: (self) => - self - .on("enter-notify-event", () => (revealer.reveal_child = true)) - .on("leave-notify-event", () => (revealer.reveal_child = false)) - .bind("visible", options.bar.position, "value", (v) => v !== "bottom"), - }); -}; diff --git a/modules/home-manager/desktops/hyprland/ags/js/icons.js b/modules/home-manager/desktops/hyprland/ags/js/icons.js deleted file mode 100644 index c02bdde..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/icons.js +++ /dev/null @@ -1,122 +0,0 @@ -export default { - lock: "system-lock-screen-symbolic", - fallback: { - executable: "application-x-executable-symbolic", - }, - audio: { - mic: { - muted: "microphone-disabled-symbolic", - low: "microphone-sensitivity-low-symbolic", - medium: "microphone-sensitivity-medium-symbolic", - high: "microphone-sensitivity-high-symbolic", - }, - volume: { - muted: "audio-volume-muted-symbolic", - low: "audio-volume-low-symbolic", - medium: "audio-volume-medium-symbolic", - high: "audio-volume-high-symbolic", - overamplified: "audio-volume-overamplified-symbolic", - }, - type: { - headset: "audio-headphones-symbolic", - speaker: "audio-speakers-symbolic", - card: "audio-card-symbolic", - }, - mixer: "", - }, - asusctl: { - profile: { - Balanced: "power-profile-balanced-symbolic", - Quiet: "power-profile-power-saver-symbolic", - Performance: "power-profile-performance-symbolic", - }, - mode: { - Integrated: "", - Hybrid: "󰢮", - }, - }, - apps: { - apps: "view-app-grid-symbolic", - search: "folder-saved-search-symbolic", - }, - battery: { - charging: "󱐋", - warning: "battery-empty-symbolic", - }, - bluetooth: { - enabled: "bluetooth-active-symbolic", - disabled: "bluetooth-disabled-symbolic", - }, - brightness: { - indicator: "display-brightness-symbolic", - keyboard: "keyboard-brightness-symbolic", - screen: "display-brightness-symbolic", - }, - powermenu: { - sleep: "weather-clear-night-symbolic", - reboot: "system-reboot-symbolic", - logout: "system-log-out-symbolic", - shutdown: "system-shutdown-symbolic", - }, - recorder: { - recording: "media-record-symbolic", - }, - notifications: { - noisy: "preferences-system-notifications-symbolic", - silent: "notifications-disabled-symbolic", - }, - trash: { - full: "user-trash-full-symbolic", - empty: "user-trash-symbolic", - }, - mpris: { - fallback: "audio-x-generic-symbolic", - shuffle: { - enabled: "󰒟", - disabled: "󰒟", - }, - loop: { - none: "󰓦", - track: "󰓦", - playlist: "󰑐", - }, - playing: "󰏦", - paused: "󰐍", - stopped: "󰐍", - prev: "󰒮", - next: "󰒭", - }, - ui: { - colorpicker: "color-select-symbolic", - close: "window-close-symbolic", - info: "info-symbolic", - menu: "open-menu-symbolic", - link: "external-link-symbolic", - settings: "emblem-system-symbolic", - tick: "object-select-symbolic", - arrow: { - right: "pan-end-symbolic", - left: "pan-start-symbolic", - down: "pan-down-symbolic", - up: "pan-up-symbolic", - }, - }, - system: { - cpu: "org.gnome.SystemMonitor-symbolic", - ram: "drive-harddisk-solidstate-symbolic", - temp: "temperature-symbolic", - }, - dialog: { - Search: "", - Applauncher: "󰵆", - Bar: "", - Border: "󰃇", - Color: "󰏘", - Desktop: "", - Font: "", - General: "󰒓", - Miscellaneous: "󰠱", - Theme: "󰃟", - Notifications: "󰂚 ", - }, -}; diff --git a/modules/home-manager/desktops/hyprland/ags/js/lockscreen/Lockscreen.js b/modules/home-manager/desktops/hyprland/ags/js/lockscreen/Lockscreen.js deleted file mode 100644 index a200b8d..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/lockscreen/Lockscreen.js +++ /dev/null @@ -1,63 +0,0 @@ -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import Avatar from "../misc/Avatar.js"; -import Lockscreen from "../services/lockscreen.js"; -import Layer from "gi://GtkLayerShell"; - -const PasswordEntry = () => - Widget.Box({ - children: [ - Widget.Entry({ - setup: (self) => self.hook(Lockscreen, () => (self.text = ""), "lock"), - visibility: false, - placeholder_text: "Password", - on_accept: ({ text }) => Lockscreen.auth(text || ""), - hpack: "center", - hexpand: true, - }), - Widget.Spinner({ - active: true, - vpack: "center", - setup: (self) => - self.hook( - Lockscreen, - (_, auth) => (self.visible = auth), - "authenticating", - ), - }), - ], - }); - -/** @param {number} monitor */ -export default (monitor) => { - const win = Widget.Window({ - name: `lockscreen${monitor}`, - class_name: "lockscreen", - monitor, - layer: "overlay", - visible: false, - setup: (self) => - self.hook(Lockscreen, (_, lock) => (self.visible = lock), "lock"), - child: Widget.Box({ - css: "min-width: 3000px; min-height: 2000px;", - class_name: "shader", - child: Widget.Box({ - class_name: "content", - vertical: true, - hexpand: true, - vexpand: true, - hpack: "center", - vpack: "center", - children: [ - Avatar({ - hpack: "center", - vpack: "center", - }), - PasswordEntry(), - ], - }), - }), - }); - - Layer.set_keyboard_mode(win, Layer.KeyboardMode.EXCLUSIVE); - return win; -}; diff --git a/modules/home-manager/desktops/hyprland/ags/js/lockscreen/auth.py b/modules/home-manager/desktops/hyprland/ags/js/lockscreen/auth.py deleted file mode 100755 index 20eab87..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/lockscreen/auth.py +++ /dev/null @@ -1,7 +0,0 @@ -#! /usr/bin/env python - -import pam -import sys -import getpass - -print(pam.authenticate(getpass.getuser(), sys.argv[1])); diff --git a/modules/home-manager/desktops/hyprland/ags/js/main.js b/modules/home-manager/desktops/hyprland/ags/js/main.js deleted file mode 100644 index 78c662c..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/main.js +++ /dev/null @@ -1,43 +0,0 @@ -import Dashboard from "./dashboard/Dashboard.js"; -import Desktop from "./desktop/Desktop.js"; -import Lockscreen from "./lockscreen/Lockscreen.js"; -import Notifications from "./notifications/Notifications.js"; -import OSD from "./osd/OSD.js"; -import Overview from "./overview/Overview.js"; -import PowerMenu from "./powermenu/PowerMenu.js"; -import QuickSettings from "./quicksettings/QuickSettings.js"; -import ScreenCorners from "./screencorner/ScreenCorners.js"; -import TopBar from "./bar/TopBar.js"; -import Verification from "./powermenu/Verification.js"; -import About from "./about/about.js"; -import { init } from "./settings/setup.js"; -import { forMonitors } from "./utils.js"; -import { initWallpaper } from "./settings/wallpaper.js"; -import options from "./options.js"; - -initWallpaper(); - -const windows = () => [ - forMonitors(Desktop), - forMonitors(Lockscreen), - forMonitors(Notifications), - forMonitors(OSD), - forMonitors(ScreenCorners), - forMonitors(TopBar), - - Dashboard(), - Overview(), - PowerMenu(), - QuickSettings(), - Verification(), - About(), -]; - -export default { - onConfigParsed: init, - windows: windows().flat(1), - closeWindowDelay: { - quicksettings: options.transition.value, - dashboard: options.transition.value, - }, -}; diff --git a/modules/home-manager/desktops/hyprland/ags/js/misc/Avatar.js b/modules/home-manager/desktops/hyprland/ags/js/misc/Avatar.js deleted file mode 100644 index e5b70f6..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/misc/Avatar.js +++ /dev/null @@ -1,16 +0,0 @@ -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import options from "../options.js"; - -/** @param {import('types/widgets/box').BoxProps=} props */ -export default (props) => - Widget.Box({ ...props, class_name: "avatar" }) - .hook(options.desktop.avatar, (box) => - box.setCss(` - background-image: url('${options.desktop.avatar.value}'); - background-size: cover; - `), - ) - .on("size-allocate", (box) => { - const h = box.get_allocated_height(); - box.set_size_request(Math.ceil(h * 1.1), -1); - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/misc/BatteryIcon.js b/modules/home-manager/desktops/hyprland/ags/js/misc/BatteryIcon.js deleted file mode 100644 index 3551a11..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/misc/BatteryIcon.js +++ /dev/null @@ -1,14 +0,0 @@ -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import Battery from "resource:///com/github/Aylur/ags/service/battery.js"; - -export default () => - Widget.Icon({ - class_name: "battery", - icon: Battery.bind("icon_name"), - setup: (icon) => - icon.hook(Battery, () => { - icon.toggleClassName("charging", Battery.charging); - icon.toggleClassName("charged", Battery.charged); - icon.toggleClassName("low", Battery.percent < 30); - }), - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/misc/Clock.js b/modules/home-manager/desktops/hyprland/ags/js/misc/Clock.js deleted file mode 100644 index 69cda04..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/misc/Clock.js +++ /dev/null @@ -1,17 +0,0 @@ -import { clock } from "../variables.js"; -import Widget from "resource:///com/github/Aylur/ags/widget.js"; - -/** - * @param {import('types/widgets/label').Props & { - * format?: string, - * interval?: number, - * }} o - */ -export default ({ format = "%H:%M:%S %B %e. %A", ...rest } = {}) => - Widget.Label({ - class_name: "clock", - label: clock.bind("value").transform((time) => { - return time.format(format) || "wrong format"; - }), - ...rest, - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/misc/FontIcon.js b/modules/home-manager/desktops/hyprland/ags/js/misc/FontIcon.js deleted file mode 100644 index e1cd61c..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/misc/FontIcon.js +++ /dev/null @@ -1,48 +0,0 @@ -import Gtk from "gi://Gtk?version=3.0"; -import { subclass, register } from "resource:///com/github/Aylur/ags/widget.js"; -import AgsLabel from "resource:///com/github/Aylur/ags/widgets/label.js"; - -class FontIcon extends AgsLabel { - static { - register(this); - } - - /** @param {string | import('types/widgets/label').Props & { icon?: string }} params */ - constructor(params = "") { - // @ts-expect-error - const { icon = "", ...rest } = params; - - super(typeof params === "string" ? {} : rest); - this.toggleClassName("font-icon"); - - if (typeof params === "object") this.icon = icon; - - if (typeof params === "string") this.icon = params; - } - - get icon() { - return this.label; - } - set icon(icon) { - this.label = icon; - } - - get size() { - return this.get_style_context().get_property( - "font-size", - Gtk.StateFlags.NORMAL, - ); - } - - /** @returns {[number, number]} */ - vfunc_get_preferred_height() { - return [this.size, this.size]; - } - - /** @returns {[number, number]} */ - vfunc_get_preferred_width() { - return [this.size, this.size]; - } -} - -export default subclass(FontIcon); diff --git a/modules/home-manager/desktops/hyprland/ags/js/misc/HoverRevealer.js b/modules/home-manager/desktops/hyprland/ags/js/misc/HoverRevealer.js deleted file mode 100644 index 30217db..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/misc/HoverRevealer.js +++ /dev/null @@ -1,62 +0,0 @@ -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; - -/** - * @typedef {import('types/widgets/eventbox').EventBoxProps & { - * indicator?: import('types/widgets/box').BoxProps['child'] - * direction?: 'left' | 'right' | 'down' | 'up' - * duration?: number - * setupRevealer?: (rev: ReturnType) => void - * setupEventBox?: (rev: ReturnType) => void - * }} HoverRevealProps - */ - -/** - * @param {HoverRevealProps} props - */ -export default ({ - indicator, - child, - direction = "left", - duration = 300, - setupEventBox, - setupRevealer, - ...rest -}) => { - let open = false; - const vertical = direction === "down" || direction === "up"; - const posStart = direction === "down" || direction === "right"; - const posEnd = direction === "up" || direction === "left"; - - const revealer = Widget.Revealer({ - transition: `slide_${direction}`, - setup: setupRevealer, - transition_duration: duration, - child, - }); - - const eventbox = Widget.EventBox({ - ...rest, - setup: setupEventBox, - on_hover: () => { - if (open) return; - - revealer.reveal_child = true; - Utils.timeout(duration, () => (open = true)); - }, - on_hover_lost: () => { - if (!open) return; - - revealer.reveal_child = false; - open = false; - }, - child: Widget.Box({ - vertical, - children: [posStart && indicator, revealer, posEnd && indicator], - }), - }); - - return Widget.Box({ - children: [eventbox], - }); -}; diff --git a/modules/home-manager/desktops/hyprland/ags/js/misc/IconBrowser.js b/modules/home-manager/desktops/hyprland/ags/js/misc/IconBrowser.js deleted file mode 100644 index bfb3b8b..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/misc/IconBrowser.js +++ /dev/null @@ -1,63 +0,0 @@ -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import RegularWindow from "./RegularWindow.js"; -import Gtk from "gi://Gtk"; - -export default () => { - const selected = Widget.Label({ - css: "font-size: 1.2em;", - }); - - const flowbox = Widget.FlowBox({ - min_children_per_line: 10, - setup: (self) => { - self.connect("child-activated", (_, child) => { - selected.label = child.get_child().iconName; - }); - - Gtk.IconTheme.get_default() - .list_icons(null) - .sort() - .map((icon) => { - !icon.endsWith(".symbolic") && - self.insert( - Widget.Icon({ - icon, - size: 38, - }), - -1, - ); - }); - - self.show_all(); - }, - }); - - const entry = Widget.Entry({ - on_change: ({ text }) => - flowbox.get_children().forEach((child) => { - child.visible = child.get_child().iconName.includes(text); - }), - }); - - return RegularWindow({ - name: "icons", - visible: true, - child: Widget.Box({ - css: "padding: 30px;", - spacing: 20, - vertical: true, - children: [ - entry, - Widget.Scrollable({ - hscroll: "never", - vscroll: "always", - hexpand: true, - vexpand: true, - css: "min-width: 500px;" + "min-height: 500px;", - child: flowbox, - }), - selected, - ], - }), - }); -}; diff --git a/modules/home-manager/desktops/hyprland/ags/js/misc/Notification.js b/modules/home-manager/desktops/hyprland/ags/js/misc/Notification.js deleted file mode 100644 index 2762ce5..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/misc/Notification.js +++ /dev/null @@ -1,132 +0,0 @@ -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; -import GLib from "gi://GLib"; - -/** @param {import('types/service/notifications').Notification} n */ -const NotificationIcon = ({ app_entry, app_icon, image }) => { - if (image) { - return Widget.Box({ - vpack: "start", - hexpand: false, - class_name: "icon img", - css: ` - background-image: url("${image}"); - background-size: cover; - background-repeat: no-repeat; - background-position: center; - min-width: 78px; - min-height: 78px; - `, - }); - } - - 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, - class_name: "icon", - css: ` - min-width: 78px; - min-height: 78px; - `, - child: Widget.Icon({ - icon, - size: 58, - hpack: "center", - hexpand: true, - vpack: "center", - vexpand: true, - }), - }); -}; - -/** @param {import('types/service/notifications').Notification} notification */ -export default (notification) => { - const content = Widget.Box({ - class_name: "content", - children: [ - NotificationIcon(notification), - Widget.Box({ - hexpand: true, - vertical: true, - children: [ - Widget.Box({ - children: [ - Widget.Label({ - class_name: "title", - xalign: 0, - justification: "left", - hexpand: true, - max_width_chars: 24, - truncate: "end", - wrap: true, - label: notification.summary, - use_markup: true, - }), - Widget.Label({ - class_name: "time", - vpack: "start", - label: GLib.DateTime.new_from_unix_local( - notification.time, - ).format("%H:%M"), - }), - Widget.Button({ - class_name: "close-button", - vpack: "start", - child: Widget.Icon("window-close-symbolic"), - on_clicked: () => notification.close(), - }), - ], - }), - Widget.Label({ - class_name: "description", - hexpand: true, - use_markup: true, - xalign: 0, - justification: "left", - label: notification.body, - wrap: true, - }), - ], - }), - ], - }); - - const actionsbox = Widget.Revealer({ - transition: "slide_down", - child: Widget.EventBox({ - child: Widget.Box({ - class_name: "actions horizontal", - children: notification.actions.map((action) => - Widget.Button({ - class_name: "action-button", - on_clicked: () => notification.invoke(action.id), - hexpand: true, - child: Widget.Label(action.label), - }), - ), - }), - }), - }); - - return Widget.EventBox({ - class_name: `notification ${notification.urgency}`, - vexpand: false, - on_primary_click: () => notification.dismiss(), - on_hover() { - actionsbox.reveal_child = true; - }, - on_hover_lost() { - actionsbox.reveal_child = true; - notification.dismiss(); - }, - child: Widget.Box({ - vertical: true, - children: [content, notification.actions.length > 0 && actionsbox], - }), - }); -}; diff --git a/modules/home-manager/desktops/hyprland/ags/js/misc/PopupWindow.js b/modules/home-manager/desktops/hyprland/ags/js/misc/PopupWindow.js deleted file mode 100644 index 48fd3e0..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/misc/PopupWindow.js +++ /dev/null @@ -1,83 +0,0 @@ -import AgsWindow from "resource:///com/github/Aylur/ags/widgets/window.js"; -import App from "resource:///com/github/Aylur/ags/app.js"; -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import options from "../options.js"; -import GObject from "gi://GObject"; - -const keyGrabber = Widget.Window({ - name: "key-grabber", - popup: true, - anchor: ["top", "left", "right", "bottom"], - css: "background-color: transparent;", - visible: false, - exclusivity: "ignore", - keymode: "on-demand", - layer: "top", - attribute: { list: [] }, - setup: (self) => - self.on("notify::visible", ({ visible }) => { - if (!visible) - self.attribute?.list.forEach((name) => App.closeWindow(name)); - }), - child: Widget.EventBox({ vexpand: true }).on("button-press-event", () => { - App.closeWindow("key-grabber"); - keyGrabber.attribute?.list.forEach((name) => App.closeWindow(name)); - }), -}); - -// add before any PopupWindow is instantiated -App.addWindow(keyGrabber); - -export class PopupWindow extends AgsWindow { - static { - GObject.registerClass(this); - } - - constructor({ name, child, transition = "none", visible = false, ...rest }) { - super({ - ...rest, - name, - popup: true, - keymode: "exclusive", - layer: "overlay", - class_names: ["popup-window", name], - }); - - child.toggleClassName("window-content"); - this.revealer = Widget.Revealer({ - transition, - child, - transition_duration: options.transition.value, - setup: (self) => - self.hook(App, (_, wname, visible) => { - if (wname === name) this.revealer.reveal_child = visible; - }), - }); - - this.child = Widget.Box({ - css: "padding: 1px;", - child: this.revealer, - }); - - this.show_all(); - this.visible = visible; - - keyGrabber.bind("visible", this, "visible"); - keyGrabber.attribute?.list.push(name); - } - - set transition(dir) { - this.revealer.transition = dir; - } - get transition() { - return this.revealer.transition; - } -} - -/** @param {import('types/widgets/window').WindowProps & { - * name: string - * child: import('types/widgets/box').default - * transition?: import('types/widgets/revealer').RevealerProps['transition'] - * }} config - */ -export default (config) => new PopupWindow(config); diff --git a/modules/home-manager/desktops/hyprland/ags/js/misc/Progress.js b/modules/home-manager/desktops/hyprland/ags/js/misc/Progress.js deleted file mode 100644 index 3b6fafb..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/misc/Progress.js +++ /dev/null @@ -1,60 +0,0 @@ -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; - -/** @param {import('types/widgets/box').BoxProps & { - * width: number - * height: number - * }} o */ -export default ({ - height = 18, - width = 180, - vertical = false, - child, - ...props -}) => { - const fill = Widget.Box({ - class_name: "fill", - hexpand: vertical, - vexpand: !vertical, - hpack: vertical ? "fill" : "start", - vpack: vertical ? "end" : "fill", - children: [child], - }); - - let fill_size = 0; - - return Widget.Box({ - ...props, - class_name: "progress", - css: ` - min-width: ${width}px; - min-height: ${height}px; - `, - children: [fill], - attribute: (value) => { - if (value < 0) return; - - const axis = vertical ? "height" : "width"; - const axisv = vertical ? height : width; - const min = vertical ? width : height; - const preferred = (axisv - min) * value + min; - - if (!fill_size) { - fill_size = preferred; - fill.setCss(`min-${axis}: ${preferred}px;`); - return; - } - - const frames = 10; - const goal = preferred - fill_size; - const step = goal / frames; - - for (let i = 0; i < frames; ++i) { - Utils.timeout(5 * i, () => { - fill_size += step; - fill.setCss(`min-${axis}: ${fill_size}px`); - }); - } - }, - }); -}; diff --git a/modules/home-manager/desktops/hyprland/ags/js/misc/RegularWindow.js b/modules/home-manager/desktops/hyprland/ags/js/misc/RegularWindow.js deleted file mode 100644 index 45a3641..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/misc/RegularWindow.js +++ /dev/null @@ -1,4 +0,0 @@ -import { subclass } from "resource:///com/github/Aylur/ags/widget.js"; -import Gtk from "gi://Gtk"; - -export default subclass(Gtk.Window, "RegularWindow"); diff --git a/modules/home-manager/desktops/hyprland/ags/js/misc/mpris.js b/modules/home-manager/desktops/hyprland/ags/js/misc/mpris.js deleted file mode 100644 index 3ef88a9..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/misc/mpris.js +++ /dev/null @@ -1,263 +0,0 @@ -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; -import icons from "../icons.js"; -import { blurImg } from "../utils.js"; - -/** - * @param {import('types/service/mpris').MprisPlayer} player - * @param {import('types/widgets/box').BoxProps=} props - */ -export const CoverArt = (player, props) => - Widget.Box({ - ...props, - class_name: "cover", - css: player - .bind("cover_path") - .transform((p) => `background-image: url("${p}")`), - }); - -/** - * @param {import('types/service/mpris').MprisPlayer} player - * @param {import('types/widgets/box').BoxProps=} props - */ -export const BlurredCoverArt = (player, props) => - Widget.Box({ - ...props, - class_name: "blurred-cover", - setup: (self) => - self.hook( - player, - (box) => - blurImg(player.cover_path).then((img) => { - img && box.setCss(`background-image: url("${img}")`); - }), - "notify::cover-path", - ), - }); - -/** - * @param {import('types/service/mpris').MprisPlayer} player - * @param {import('types/widgets/label').Props=} props - */ -export const TitleLabel = (player, props) => - Widget.Label({ - ...props, - class_name: "title", - label: player.bind("track_title"), - }); - -/** - * @param {import('types/service/mpris').MprisPlayer} player - * @param {import('types/widgets/label').Props=} props - */ -export const ArtistLabel = (player, props) => - Widget.Label({ - ...props, - class_name: "artist", - label: player.bind("track_artists").transform((a) => a.join(", ") || ""), - }); - -/** - * @param {import('types/service/mpris').MprisPlayer} player - * @param {import('types/widgets/icon').Props & { symbolic?: boolean }=} props - */ -export const PlayerIcon = (player, { symbolic = true, ...props } = {}) => - Widget.Icon({ - ...props, - class_name: "player-icon", - tooltip_text: player.identity || "", - setup: (self) => - self.hook(player, (icon) => { - const name = `${player.entry}${symbolic ? "-symbolic" : ""}`; - Utils.lookUpIcon(name) - ? (icon.icon = name) - : (icon.icon = icons.mpris.fallback); - }), - }); - -/** - * @param {import('types/service/mpris').MprisPlayer} player - * @param {import('types/widgets/slider').SliderProps=} props - */ -export const PositionSlider = (player, props) => - Widget.Slider({ - ...props, - class_name: "position-slider", - draw_value: false, - on_change: ({ value }) => (player.position = player.length * value), - setup: (self) => { - const update = () => { - if (self.dragging) return; - - self.visible = player.length > 0; - if (player.length > 0) self.value = player.position / player.length; - }; - self.hook(player, update); - self.hook(player, update, "position"); - self.poll(1000, update); - }, - }); - -/** @param {number} length */ -function lengthStr(length) { - const min = Math.floor(length / 60); - const sec = Math.floor(length % 60); - const sec0 = sec < 10 ? "0" : ""; - return `${min}:${sec0}${sec}`; -} - -/** @param {import('types/service/mpris').MprisPlayer} player */ -export const PositionLabel = (player) => - Widget.Label({ - setup: (self) => { - const update = (_, time) => { - player.length > 0 - ? (self.label = lengthStr(time || player.position)) - : (self.visible = !!player); - }; - self.hook(player, update, "position"); - self.poll(1000, update); - }, - }); - -/** @param {import('types/service/mpris').MprisPlayer} player */ -export const LengthLabel = (player) => - Widget.Label({ - label: player.bind("length").transform((l) => lengthStr(l)), - visible: player.bind("length").transform((l) => l > 0), - }); - -/** @param {import('types/service/mpris').MprisPlayer} player */ -export const Slash = (player) => - Widget.Label({ - label: "/", - visible: player.bind("length").transform((l) => l > 0), - }); - -/** - * @param {Object} o - * @param {import('types/service/mpris').MprisPlayer} o.player - * @param {import('types/widgets/stack').StackProps['children']} o.children - * @param {'shuffle' | 'loop' | 'playPause' | 'previous' | 'next'} o.onClick - * @param {string} o.prop - * @param {string} o.canProp - * @param {any} o.cantValue - */ -const PlayerButton = ({ - player, - children, - onClick, - prop, - canProp, - cantValue, -}) => - Widget.Button({ - child: Widget.Stack({ children }).bind( - "shown", - player, - prop, - (p) => `${p}`, - ), - on_clicked: () => player[onClick](), - visible: player.bind(canProp).transform((c) => c !== cantValue), - }); - -/** @param {import('types/service/mpris').MprisPlayer} player */ -export const ShuffleButton = (player) => - PlayerButton({ - player, - children: { - true: Widget.Label({ - class_name: "shuffle enabled", - label: icons.mpris.shuffle.enabled, - }), - false: Widget.Label({ - class_name: "shuffle disabled", - label: icons.mpris.shuffle.disabled, - }), - }, - onClick: "shuffle", - prop: "shuffle-status", - canProp: "shuffle-status", - cantValue: null, - }); - -/** @param {import('types/service/mpris').MprisPlayer} player */ -export const LoopButton = (player) => - PlayerButton({ - player, - children: { - None: Widget.Label({ - class_name: "loop none", - label: icons.mpris.loop.none, - }), - Track: Widget.Label({ - class_name: "loop track", - label: icons.mpris.loop.track, - }), - Playlist: Widget.Label({ - class_name: "loop playlist", - label: icons.mpris.loop.playlist, - }), - }, - onClick: "loop", - prop: "loop-status", - canProp: "loop-status", - cantValue: null, - }); - -/** @param {import('types/service/mpris').MprisPlayer} player */ -export const PlayPauseButton = (player) => - PlayerButton({ - player, - children: { - Playing: Widget.Label({ - class_name: "playing", - label: icons.mpris.playing, - }), - Paused: Widget.Label({ - class_name: "paused", - label: icons.mpris.paused, - }), - Stopped: Widget.Label({ - class_name: "stopped", - label: icons.mpris.stopped, - }), - }, - onClick: "playPause", - prop: "play-back-status", - canProp: "can-play", - cantValue: false, - }); - -/** @param {import('types/service/mpris').MprisPlayer} player */ -export const PreviousButton = (player) => - PlayerButton({ - player, - children: { - true: Widget.Label({ - class_name: "previous", - label: icons.mpris.prev, - }), - }, - onClick: "previous", - prop: "can-go-prev", - canProp: "can-go-prev", - cantValue: false, - }); - -/** @param {import('types/service/mpris').MprisPlayer} player */ -export const NextButton = (player) => - PlayerButton({ - player, - children: { - true: Widget.Label({ - class_name: "next", - label: icons.mpris.next, - }), - }, - onClick: "next", - prop: "can-go-next", - canProp: "can-go-next", - cantValue: false, - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/notifications/Notifications.js b/modules/home-manager/desktops/hyprland/ags/js/notifications/Notifications.js deleted file mode 100644 index 60ab9d9..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/notifications/Notifications.js +++ /dev/null @@ -1,68 +0,0 @@ -import Notifications from "resource:///com/github/Aylur/ags/service/notifications.js"; -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; -import Notification from "../misc/Notification.js"; -import options from "../options.js"; - -/** @param {import('types/widgets/revealer').default} parent */ -const Popups = (parent) => { - const map = new Map(); - - const onDismissed = (_, id, force = false) => { - if (!id || !map.has(id)) return; - - if (map.get(id).isHovered() && !force) return; - - if (map.size - 1 === 0) parent.reveal_child = false; - - Utils.timeout(200, () => { - map.get(id)?.destroy(); - map.delete(id); - }); - }; - - /** @param {import('types/widgets/box').default} box */ - const onNotified = (box, id) => { - if (!id || Notifications.dnd) return; - - const n = Notifications.getNotification(id); - if (!n) return; - - if (options.notifications.black_list.value.includes(n.app_name || "")) - return; - - map.delete(id); - map.set(id, Notification(n)); - box.children = Array.from(map.values()).reverse(); - Utils.timeout(10, () => { - parent.reveal_child = true; - }); - }; - - return Widget.Box({ vertical: true }) - .hook(Notifications, onNotified, "notified") - .hook(Notifications, onDismissed, "dismissed") - .hook(Notifications, (box, id) => onDismissed(box, id, true), "closed"); -}; - -/** @param {import('types/widgets/revealer').RevealerProps['transition']} transition */ -const PopupList = (transition = "slide_down") => - Widget.Box({ - css: "padding: 1px", - children: [ - Widget.Revealer({ - transition, - setup: (self) => (self.child = Popups(self)), - }), - ], - }); - -/** @param {number} monitor */ -export default (monitor) => - Widget.Window({ - monitor, - name: `notifications${monitor}`, - class_name: "notifications", - anchor: options.notifications.position.bind("value"), - child: PopupList(), - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/options.js b/modules/home-manager/desktops/hyprland/ags/js/options.js deleted file mode 100644 index 3c574fa..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/options.js +++ /dev/null @@ -1,303 +0,0 @@ -/** - * An object holding Options that are Variables with cached values. - * - * to update an option at runtime simply run - * ags -r "options.path.to.option.setValue('value')" - * - * resetting: - * ags -r "options.reset()" - */ - -import { - Option, - resetOptions, - getValues, - apply, - getOptions, -} from "./settings/option.js"; -import { USER } from "resource:///com/github/Aylur/ags/utils.js"; -import themes from "./themes.js"; - -export default { - reset: resetOptions, - values: getValues, - apply: apply, - list: getOptions, - - spacing: Option(9), - padding: Option(8), - radii: Option(9), - - popover_padding_multiplier: Option(1.4, { - category: "General", - note: "popover-padding: padding × this", - type: "float", - unit: "", - }), - - color: { - red: Option("#e55f86", { scss: "red" }), - green: Option("#00D787", { scss: "green" }), - yellow: Option("#EBFF71", { scss: "yellow" }), - blue: Option("#51a4e7", { scss: "blue" }), - magenta: Option("#9077e7", { scss: "magenta" }), - teal: Option("#51e6e6", { scss: "teal" }), - orange: Option("#E79E64", { scss: "orange" }), - }, - - theme: { - name: Option(themes[0].name, { - category: "exclude", - note: "Name to show as active in quicktoggles", - }), - - icon: Option(themes[0].icon, { - category: "exclude", - note: "Icon to show as active in quicktoggles", - }), - - scheme: Option("dark", { - enums: ["dark", "light"], - type: "enum", - note: "Color scheme to set on Gtk apps: 'ligth' or 'dark'", - title: "Color Scheme", - scss: "color-scheme", - }), - bg: Option("#171717", { - title: "Background Color", - scss: "bg-color", - }), - fg: Option("#eeeeee", { - title: "Foreground Color", - scss: "fg-color", - }), - - accent: { - accent: Option("$blue", { - category: "Theme", - title: "Accent Color", - scss: "accent", - }), - fg: Option("#141414", { - category: "Theme", - title: "Accent Foreground Color", - scss: "accent-fg", - }), - gradient: Option("to right, $accent, lighten($accent, 6%)", { - category: "Theme", - title: "Accent Linear Gradient", - scss: "accent-gradient", - }), - }, - - widget: { - bg: Option("$fg-color", { - category: "Theme", - title: "Widget Background Color", - scss: "_widget-bg", - }), - opacity: Option(94, { - category: "Theme", - title: "Widget Background Opacity", - unit: "", - scss: "widget-opacity", - }), - }, - }, - - border: { - color: Option("$fg-color", { - category: "Border", - title: "Border Color", - scss: "_border-color", - }), - opacity: Option(97, { - category: "Border", - title: "Border Opacity", - unit: "", - }), - width: Option(1, { - category: "Border", - title: "Border Width", - }), - }, - - hypr: { - inactive_border: Option("rgba(333333ff)", { - category: "Border", - title: "Border on Inactive Windows", - scss: "exclude", - }), - wm_gaps_multiplier: Option(2.4, { - category: "General", - scss: "wm-gaps-multiplier", - note: "wm-gaps: padding × this", - type: "float", - unit: "", - }), - }, - - // TODO: use this on revealers - transition: Option(200, { - category: "exclude", - note: "Transition time on aminations in ms, e.g on hover", - unit: "ms", - }), - - font: { - font: Option("Ubuntu Nerd Font", { - type: "font", - title: "Font", - scss: "font", - }), - mono: Option("Mononoki Nerd Font", { - title: "Monospaced Font", - scss: "mono-font", - }), - size: Option(13, { - scss: "font-size", - unit: "pt", - }), - }, - - applauncher: { - width: Option(500), - height: Option(500), - icon_size: Option(52), - }, - - bar: { - position: Option("top", { - enums: ["top", "bottom"], - type: "enum", - }), - style: Option("normal", { - enums: ["floating", "normal", "separated"], - type: "enum", - }), - flat_buttons: Option(true, { scss: "bar-flat-buttons" }), - separators: Option(true), - icon: Option("distro-icon", { - note: '"distro-icon" or a single font', - }), - }, - - battery: { - show_percentage: Option(true, { - persist: true, - noReload: false, - category: "exclude", - }), - bar: { - show_icon: Option(true, { category: "Bar" }), - width: Option(70, { category: "Bar" }), - height: Option(14, { category: "Bar" }), - full: Option(false, { category: "Bar" }), - }, - low: Option(30, { category: "Bar" }), - medium: Option(50, { category: "Bar" }), - }, - - desktop: { - wallpaper: { - fg: Option("#fff", { scss: "wallpaper-fg" }), - img: Option(themes[0].options["desktop.wallpaper.img"], { - scssFormat: (v) => `"${v}"`, - type: "img", - }), - }, - avatar: Option(`/var/lib/AccountsService/icons/${USER}`, { - scssFormat: (v) => `"${v}"`, - type: "img", - note: "displayed in quicksettings and locksreen", - }), - screen_corners: Option(true, { scss: "screen-corners" }), - clock: { - enable: Option(true), - position: Option("center center", { - note: "halign valign", - }), - }, - drop_shadow: Option(true, { scss: "drop-shadow" }), - shadow: Option("rgba(0, 0, 0, .6)", { scss: "shadow" }), - dock: { - icon_size: Option(56), - pinned_apps: Option( - [ - "firefox", - "org.wezfurlong.wezterm", - "org.gnome.Nautilus", - "org.gnome.Calendar", - "obsidian", - "transmission-gtk", - "caprine", - "teams-for-linux", - "discord", - "spotify", - "com.usebottles.bottles", - "org.gnome.Software", - ], - { scss: "exclude" }, - ), - }, - }, - - notifications: { - black_list: Option(["Spotify"], { note: "app-name | entry" }), - position: Option(["top"], { note: "anchor" }), - width: Option(450), - }, - - dashboard: { - sys_info_size: Option(70, { - category: "Desktop", - scss: "sys-info-size", - }), - }, - - mpris: { - black_list: Option(["Caprine"], { - category: "Bar", - title: "List of blacklisted mpris players", - note: "filters for bus-name, name, identity, entry", - }), - preferred: Option("spotify", { - category: "Bar", - title: "Preferred player", - }), - }, - - workspaces: Option(10, { - category: "Bar", - title: "No. workspaces on bar and overview", - note: "Set it to 0 to make it dynamic", - }), - - temperature: "/sys/class/thermal/thermal_zone0/temp", - systemFetchInterval: 5000, - brightnessctlKBD: "asus::kbd_backlight", - substitutions: { - icons: [ - ["transmission-gtk", "transmission"], - ["blueberry.py", "bluetooth"], - ["Caprine", "facebook-messenger"], - ["", "preferences-desktop-display"], - ], - titles: [ - ["com.github.Aylur.ags", "AGS"], - ["transmission-gtk", "Transmission"], - ["com.obsproject.Studio", "OBS"], - ["com.usebottles.bottles", "Bottles"], - ["com.github.wwmm.easyeffects", "Easy Effects"], - ["org.gnome.TextEditor", "Text Editor"], - ["org.gnome.design.IconLibrary", "Icon Library"], - ["blueberry.py", "Blueberry"], - ["org.wezfurlong.wezterm", "Wezterm"], - ["com.raggesilver.BlackBox", "BlackBox"], - ["firefox", "Firefox"], - ["org.gnome.Nautilus", "Files"], - ["libreoffice-writer", "Writer"], - ["", "Desktop"], - ], - }, -}; diff --git a/modules/home-manager/desktops/hyprland/ags/js/osd/OSD.js b/modules/home-manager/desktops/hyprland/ags/js/osd/OSD.js deleted file mode 100644 index 8c6ee0a..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/osd/OSD.js +++ /dev/null @@ -1,60 +0,0 @@ -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; -import FontIcon from "../misc/FontIcon.js"; -import Progress from "../misc/Progress.js"; -import Indicator from "../services/onScreenIndicator.js"; - -export const OnScreenIndicator = ({ height = 300, width = 48 } = {}) => - Widget.Box({ - class_name: "indicator", - css: "padding: 1px;", - child: Widget.Revealer({ - transition: "slide_left", - setup: (self) => - self.hook(Indicator, (_, value) => { - self.reveal_child = value > -1; - }), - child: Progress({ - width, - height, - vertical: true, - setup: (self) => - self.hook(Indicator, (_, value) => self.attribute(value)), - child: Widget.Stack({ - vpack: "start", - hpack: "center", - hexpand: false, - children: { - true: Widget.Icon({ - hpack: "center", - size: width, - setup: (w) => - w.hook(Indicator, (_, _v, name) => (w.icon = name || "")), - }), - false: FontIcon({ - hpack: "center", - hexpand: true, - css: `font-size: ${width}px;`, - setup: (w) => - w.hook(Indicator, (_, _v, name) => (w.label = name || "")), - }), - }, - setup: (self) => - self.hook(Indicator, (_, _v, name) => { - self.shown = `${!!Utils.lookUpIcon(name)}`; - }), - }), - }), - }), - }); - -/** @param {number} monitor */ -export default (monitor) => - Widget.Window({ - name: `indicator${monitor}`, - monitor, - class_name: "indicator", - layer: "overlay", - anchor: ["right"], - child: OnScreenIndicator(), - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/overview/Client.js b/modules/home-manager/desktops/hyprland/ags/js/overview/Client.js deleted file mode 100644 index 8c89eac..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/overview/Client.js +++ /dev/null @@ -1,57 +0,0 @@ -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import App from "resource:///com/github/Aylur/ags/app.js"; -import { createSurfaceFromWidget, substitute } from "../utils.js"; -import Gdk from "gi://Gdk"; -import Gtk from "gi://Gtk?version=3.0"; -import options from "../options.js"; -import Hyprland from "resource:///com/github/Aylur/ags/service/hyprland.js"; -import icons from "../icons.js"; - -const SCALE = 0.08; -const TARGET = [Gtk.TargetEntry.new("text/plain", Gtk.TargetFlags.SAME_APP, 0)]; - -/** @param {string} args */ -const dispatch = (args) => Hyprland.sendMessage(`dispatch ${args}`); - -/** @param {string} str */ -const icon = (str) => { - const icon = substitute(options.substitutions.icons, str); - if (Utils.lookUpIcon(icon)) return icon; - - console.warn("no icon", icon); - return icons.fallback.executable; -}; - -export default ({ address, size: [w, h], class: c, title }) => - Widget.Button({ - class_name: "client", - tooltip_text: `${title}`, - child: Widget.Icon({ - css: ` - min-width: ${w * SCALE}px; - min-height: ${h * SCALE}px; - `, - icon: icon(c), - }), - on_secondary_click: () => dispatch(`closewindow address:${address}`), - on_clicked: () => - dispatch(`focuswindow address:${address}`).then(() => - App.closeWindow("overview"), - ), - - setup: (btn) => - btn - .on("drag-data-get", (_w, _c, data) => - data.set_text(address, address.length), - ) - .on("drag-begin", (_, context) => { - Gtk.drag_set_icon_surface(context, createSurfaceFromWidget(btn)); - btn.toggleClassName("hidden", true); - }) - .on("drag-end", () => btn.toggleClassName("hidden", false)) - .drag_source_set( - Gdk.ModifierType.BUTTON1_MASK, - TARGET, - Gdk.DragAction.COPY, - ), - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/overview/Overview.js b/modules/home-manager/desktops/hyprland/ags/js/overview/Overview.js deleted file mode 100644 index 6812108..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/overview/Overview.js +++ /dev/null @@ -1,52 +0,0 @@ -import Hyprland from "resource:///com/github/Aylur/ags/service/hyprland.js"; -import PopupWindow from "../misc/PopupWindow.js"; -import Workspace from "./Workspace.js"; -import options from "../options.js"; -import { range } from "../utils.js"; - -const ws = options.workspaces; - -const Overview = () => - Widget.Box({ - children: [Workspace(0)], // for type infer - setup: (self) => - Utils.idle(() => { - self.hook(ws, () => { - self.children = range(ws.value).map(Workspace); - update(self); - children(self); - }); - self.hook(Hyprland, update); - self.hook(Hyprland, children, "notify::workspaces"); - update(self); - children(self); - }), - }); - -/** @param {ReturnType} box */ -const update = (box) => { - if (!box.get_parent()?.visible) return; - - Hyprland.sendMessage("j/clients") - .then((clients) => { - box.children.forEach((ws) => { - ws.attribute(JSON.parse(clients)); - }); - }) - .catch(console.error); -}; - -/** @param {import('types/widgets/box').default} box */ -const children = (box) => { - if (ws.value === 0) { - box.children = Hyprland.workspaces - .sort((ws1, ws2) => ws1.id - ws2.id) - .map(({ id }) => Workspace(id)); - } -}; - -export default () => - PopupWindow({ - name: "overview", - child: Overview(), - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/overview/Workspace.js b/modules/home-manager/desktops/hyprland/ags/js/overview/Workspace.js deleted file mode 100644 index 2a9ed84..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/overview/Workspace.js +++ /dev/null @@ -1,60 +0,0 @@ -import Hyprland from "resource:///com/github/Aylur/ags/service/hyprland.js"; -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; -import Gdk from "gi://Gdk"; -import Gtk from "gi://Gtk?version=3.0"; -import Client from "./Client.js"; - -const SCALE = 0.08; -const TARGET = [Gtk.TargetEntry.new("text/plain", Gtk.TargetFlags.SAME_APP, 0)]; - -/** @param {string} args */ -const dispatch = (args) => Utils.execAsync(`hyprctl dispatch ${args}`); - -/** @param {number} index */ -export default (index) => { - const fixed = Gtk.Fixed.new(); - - return Widget.Box({ - class_name: "workspace", - vpack: "center", - css: ` - min-width: ${3840 * SCALE}px; - min-height: ${2160 * SCALE}px; - `, - setup: (box) => - box.hook(Hyprland, () => { - box.toggleClassName("active", Hyprland.active.workspace.id === index); - }), - child: Widget.EventBox({ - hexpand: true, - vexpand: true, - on_primary_click: () => dispatch(`workspace ${index}`), - setup: (eventbox) => { - eventbox.drag_dest_set( - Gtk.DestDefaults.ALL, - TARGET, - Gdk.DragAction.COPY, - ); - eventbox.connect("drag-data-received", (_w, _c, _x, _y, data) => { - dispatch(`movetoworkspacesilent ${index},address:${data.get_text()}`); - }); - }, - child: fixed, - }), - - /** @param {Array} clients */ - attribute: (clients) => { - fixed.get_children().forEach((ch) => ch.destroy()); - clients - .filter(({ workspace: { id } }) => id === index) - .forEach((c) => { - c.at[0] -= Hyprland.getMonitor(c.monitor)?.x || 0; - c.at[1] -= Hyprland.getMonitor(c.monitor)?.y || 0; - c.mapped && fixed.put(Client(c), c.at[0] * SCALE, c.at[1] * SCALE); - }); - - fixed.show_all(); - }, - }); -}; diff --git a/modules/home-manager/desktops/hyprland/ags/js/powermenu/PowerMenu.js b/modules/home-manager/desktops/hyprland/ags/js/powermenu/PowerMenu.js deleted file mode 100644 index 1b25fbd..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/powermenu/PowerMenu.js +++ /dev/null @@ -1,31 +0,0 @@ -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import icons from "../icons.js"; -import PowerMenu from "../services/powermenu.js"; -import ShadedPopup from "./ShadedPopup.js"; - -/** - * @param {'sleep' | 'reboot' | 'logout' | 'shutdown'} action - * @param {string} label - */ -const SysButton = (action, label) => - Widget.Button({ - on_clicked: () => PowerMenu.action(action), - child: Widget.Box({ - vertical: true, - children: [Widget.Icon(icons.powermenu[action]), Widget.Label(label)], - }), - }); - -export default () => - ShadedPopup({ - name: "powermenu", - expand: true, - child: Widget.Box({ - children: [ - SysButton("sleep", "Sleep"), - SysButton("reboot", "Reboot"), - SysButton("logout", "Log Out"), - SysButton("shutdown", "Shutdown"), - ], - }), - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/powermenu/ShadedPopup.js b/modules/home-manager/desktops/hyprland/ags/js/powermenu/ShadedPopup.js deleted file mode 100644 index 606fe70..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/powermenu/ShadedPopup.js +++ /dev/null @@ -1,44 +0,0 @@ -import App from "resource:///com/github/Aylur/ags/app.js"; -import Widget from "resource:///com/github/Aylur/ags/widget.js"; - -/** @param {string} windowName */ -const Padding = (windowName) => - Widget.EventBox({ - class_name: "padding", - hexpand: true, - vexpand: true, - setup: (w) => - w.on("button-press-event", () => App.toggleWindow(windowName)), - }); - -/** - * @template {import('gi://Gtk?version=3.0').default.Widget} T - * @param {import('types/widgets/window').WindowProps & { - * name: string - * child: import('types/widgets/box').default - * }} o - */ -export default ({ name, child, ...rest }) => - Widget.Window({ - ...rest, - class_names: ["popup-window", name], - name, - visible: false, - popup: true, - keymode: "on-demand", - setup() { - child.toggleClassName("window-content"); - }, - child: Widget.CenterBox({ - class_name: "shader", - css: "min-width: 5000px; min-height: 3000px;", - start_widget: Padding(name), - end_widget: Padding(name), - center_widget: Widget.CenterBox({ - vertical: true, - start_widget: Padding(name), - end_widget: Padding(name), - center_widget: child, - }), - }), - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/powermenu/Verification.js b/modules/home-manager/desktops/hyprland/ags/js/powermenu/Verification.js deleted file mode 100644 index 02555b4..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/powermenu/Verification.js +++ /dev/null @@ -1,46 +0,0 @@ -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import App from "resource:///com/github/Aylur/ags/app.js"; -import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; -import PowerMenu from "../services/powermenu.js"; -import ShadedPopup from "./ShadedPopup.js"; - -export default () => - ShadedPopup({ - name: "verification", - expand: true, - child: Widget.Box({ - vertical: true, - children: [ - Widget.Box({ - class_name: "text-box", - vertical: true, - children: [ - Widget.Label({ - class_name: "title", - label: PowerMenu.bind("title"), - }), - Widget.Label({ - class_name: "desc", - label: "Are you sure?", - }), - ], - }), - Widget.Box({ - class_name: "buttons horizontal", - vexpand: true, - vpack: "end", - homogeneous: true, - children: [ - Widget.Button({ - child: Widget.Label("No"), - on_clicked: () => App.toggleWindow("verification"), - }), - Widget.Button({ - child: Widget.Label("Yes"), - on_clicked: () => Utils.exec(PowerMenu.cmd), - }), - ], - }), - ], - }), - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/quicksettings/QuickSettings.js b/modules/home-manager/desktops/hyprland/ags/js/quicksettings/QuickSettings.js deleted file mode 100644 index d714851..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/quicksettings/QuickSettings.js +++ /dev/null @@ -1,65 +0,0 @@ -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import Header from "./widgets/Header.js"; -import PopupWindow from "../misc/PopupWindow.js"; -import { Volume, Microhone, SinkSelector, AppMixer } from "./widgets/Volume.js"; -import { NetworkToggle, WifiSelection } from "./widgets/Network.js"; -import { BluetoothToggle, BluetoothDevices } from "./widgets/Bluetooth.js"; -import { ThemeToggle, ThemeSelector } from "./widgets/Theme.js"; -import { ProfileToggle, ProfileSelector } from "./widgets/AsusProfile.js"; -import Media from "./widgets/Media.js"; -import Brightness from "./widgets/Brightness.js"; -import DND from "./widgets/DND.js"; -import MicMute from "./widgets/MicMute.js"; -import options from "../options.js"; - -const Row = (toggles = [], menus = []) => - Widget.Box({ - vertical: true, - children: [ - Widget.Box({ - class_name: "row horizontal", - children: toggles, - }), - ...menus, - ], - }); - -const Homogeneous = (toggles) => - Widget.Box({ - homogeneous: true, - children: toggles, - }); - -export default () => - PopupWindow({ - name: "quicksettings", - setup: (self) => - self.hook(options.bar.position, () => { - self.anchor = ["right", options.bar.position.value]; - if (options.bar.position.value === "top") - self.transition = "slide_down"; - - if (options.bar.position.value === "bottom") - self.transition = "slide_up"; - }), - child: Widget.Box({ - vertical: true, - children: [ - Header(), - Widget.Box({ - class_name: "sliders-box vertical", - vertical: true, - children: [ - Row([Volume()], [SinkSelector(), AppMixer()]), - Microhone(), - Brightness(), - ], - }), - Row( - [Homogeneous([ThemeToggle(), BluetoothToggle()]), MicMute()], - [ThemeSelector(), BluetoothDevices()], - ), - Media(), - ], - }), - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/quicksettings/ToggleButton.js b/modules/home-manager/desktops/hyprland/ags/js/quicksettings/ToggleButton.js deleted file mode 100644 index 743af09..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/quicksettings/ToggleButton.js +++ /dev/null @@ -1,134 +0,0 @@ -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import App from "resource:///com/github/Aylur/ags/app.js"; -import Variable from "resource:///com/github/Aylur/ags/variable.js"; -import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; -import icons from "../icons.js"; - -/** name of the currently opened menu */ -export const opened = Variable(""); -App.connect("window-toggled", (_, name, visible) => { - if (name === "quicksettings" && !visible) - Utils.timeout(500, () => (opened.value = "")); -}); - -/** - * @param {string} name - menu name - * @param {(() => void) | false=} activate - */ -export const Arrow = (name, activate) => { - let deg = 0; - let iconOpened = false; - const icon = Widget.Icon(icons.ui.arrow.right).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, - on_clicked: () => { - opened.value = opened.value === name ? "" : name; - if (typeof activate === "function") activate(); - }, - }); -}; - -/** - * @param {Object} o - * @param {string} o.name - menu name - * @param {import('gi://Gtk').Gtk.Widget} o.icon - * @param {import('gi://Gtk').Gtk.Widget} o.label - * @param {() => void} o.activate - * @param {() => void} o.deactivate - * @param {boolean=} o.activateOnArrow - * @param {[import('gi://GObject').GObject.Object, () => boolean]} o.connection - */ -export const ArrowToggleButton = ({ - name, - icon, - label, - activate, - deactivate, - activateOnArrow = true, - connection: [service, condition], -}) => - Widget.Box({ - class_name: "toggle-button", - setup: (self) => - self.hook(service, () => { - self.toggleClassName("active", condition()); - }), - children: [ - Widget.Button({ - child: Widget.Box({ - hexpand: true, - class_name: "label-box horizontal", - children: [icon, label], - }), - on_clicked: () => { - if (condition()) { - deactivate(); - if (opened.value === name) opened.value = ""; - } else { - activate(); - } - }, - }), - Arrow(name, activateOnArrow && activate), - ], - }); - -/** - * @param {Object} o - * @param {string} o.name - menu name - * @param {import('gi://Gtk').Gtk.Widget} o.icon - * @param {import('gi://Gtk').Gtk.Widget} o.title - * @param {import('gi://Gtk').Gtk.Widget[]} o.content - */ -export const Menu = ({ name, icon, title, content }) => - Widget.Revealer({ - transition: "slide_down", - reveal_child: opened.bind().transform((v) => v === name), - child: Widget.Box({ - class_names: ["menu", name], - vertical: true, - children: [ - Widget.Box({ - class_name: "title horizontal", - children: [icon, title], - }), - Widget.Separator(), - ...content, - ], - }), - }); - -/** - * @param {Object} o - * @param {import('gi://Gtk').Gtk.Widget} o.icon - * @param {() => void} o.toggle - * @param {[import('gi://GObject').GObject.Object, () => boolean]} o.connection - */ -export const SimpleToggleButton = ({ - icon, - toggle, - connection: [service, condition], -}) => - Widget.Button({ - class_name: "simple-toggle", - setup: (self) => - self.hook(service, () => { - self.toggleClassName("active", condition()); - }), - child: icon, - on_clicked: toggle, - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/quicksettings/widgets/AsusProfile.js b/modules/home-manager/desktops/hyprland/ags/js/quicksettings/widgets/AsusProfile.js deleted file mode 100644 index c9b0abc..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/quicksettings/widgets/AsusProfile.js +++ /dev/null @@ -1,61 +0,0 @@ -import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import icons from "../../icons.js"; -import Asusctl from "../../services/asusctl.js"; -import { ArrowToggleButton, Menu } from "../ToggleButton.js"; - -export const ProfileToggle = () => - ArrowToggleButton({ - name: "asusctl-profile", - icon: Widget.Icon({ - icon: Asusctl.bind("profile").transform((p) => icons.asusctl.profile[p]), - }), - label: Widget.Label({ - label: Asusctl.bind("profile"), - }), - connection: [Asusctl, () => Asusctl.profile !== "Balanced"], - activate: () => Asusctl.setProfile("Quiet"), - deactivate: () => Asusctl.setProfile("Balanced"), - activateOnArrow: false, - }); - -export const ProfileSelector = () => - Menu({ - name: "asusctl-profile", - icon: Widget.Icon({ - icon: Asusctl.bind("profile").transform((p) => icons.asusctl.profile[p]), - }), - title: Widget.Label("Profile Selector"), - content: [ - Widget.Box({ - vertical: true, - hexpand: true, - children: [ - Widget.Box({ - vertical: true, - children: Asusctl.profiles.map((prof) => - Widget.Button({ - on_clicked: () => Asusctl.setProfile(prof), - child: Widget.Box({ - children: [ - Widget.Icon(icons.asusctl.profile[prof]), - Widget.Label(prof), - ], - }), - }), - ), - }), - ], - }), - Widget.Separator(), - Widget.Button({ - on_clicked: () => Utils.execAsync("rog-control-center"), - child: Widget.Box({ - children: [ - Widget.Icon(icons.ui.settings), - Widget.Label("Rog Control Center"), - ], - }), - }), - ], - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/quicksettings/widgets/Bluetooth.js b/modules/home-manager/desktops/hyprland/ags/js/quicksettings/widgets/Bluetooth.js deleted file mode 100644 index 06b6e68..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/quicksettings/widgets/Bluetooth.js +++ /dev/null @@ -1,74 +0,0 @@ -import Bluetooth from "resource:///com/github/Aylur/ags/service/bluetooth.js"; -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import icons from "../../icons.js"; -import { Menu, ArrowToggleButton } from "../ToggleButton.js"; - -export const BluetoothToggle = () => - ArrowToggleButton({ - name: "bluetooth", - icon: Widget.Icon({ - icon: Bluetooth.bind("enabled").transform( - (p) => icons.bluetooth[p ? "enabled" : "disabled"], - ), - }), - label: Widget.Label({ - truncate: "end", - setup: (self) => - self.hook(Bluetooth, () => { - if (!Bluetooth.enabled) return (self.label = "Disabled"); - - if (Bluetooth.connected_devices.length === 0) - return (self.label = "Not Connected"); - - if (Bluetooth.connected_devices.length === 1) - return (self.label = Bluetooth.connected_devices[0].alias); - - self.label = `${Bluetooth.connected_devices.length} Connected`; - }), - }), - connection: [Bluetooth, () => Bluetooth.enabled], - deactivate: () => (Bluetooth.enabled = false), - activate: () => (Bluetooth.enabled = true), - }); - -/** @param {import('types/service/bluetooth').BluetoothDevice} device */ -const DeviceItem = (device) => - Widget.Box({ - children: [ - Widget.Icon(device.icon_name + "-symbolic"), - Widget.Label(device.name), - Widget.Label({ - label: `${device.battery_percentage}%`, - visible: device.bind("battery_percentage").transform((p) => p > 0), - }), - Widget.Box({ hexpand: true }), - Widget.Spinner({ - active: device.bind("connecting"), - visible: device.bind("connecting"), - }), - Widget.Switch({ - active: device.connected, - visible: device.bind("connecting").transform((p) => !p), - setup: (self) => - self.on("notify::active", () => { - device.setConnection(self.active); - }), - }), - ], - }); - -export const BluetoothDevices = () => - Menu({ - name: "bluetooth", - icon: Widget.Icon(icons.bluetooth.disabled), - title: Widget.Label("Bluetooth"), - content: [ - Widget.Box({ - hexpand: true, - vertical: true, - children: Bluetooth.bind("devices").transform((ds) => - ds.filter((d) => d.name).map(DeviceItem), - ), - }), - ], - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/quicksettings/widgets/Brightness.js b/modules/home-manager/desktops/hyprland/ags/js/quicksettings/widgets/Brightness.js deleted file mode 100644 index 309aae9..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/quicksettings/widgets/Brightness.js +++ /dev/null @@ -1,24 +0,0 @@ -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import icons from "../../icons.js"; -import Brightness from "../../services/brightness.js"; - -const BrightnessSlider = () => - Widget.Slider({ - draw_value: false, - hexpand: true, - value: Brightness.bind("screen"), - on_change: ({ value }) => (Brightness.screen = value), - }); - -export default () => - Widget.Box({ - children: [ - Widget.Button({ - child: Widget.Icon(icons.brightness.indicator), - tooltip_text: Brightness.bind("screen").transform( - (v) => `Screen Brightness: ${Math.floor(v * 100)}%`, - ), - }), - BrightnessSlider(), - ], - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/quicksettings/widgets/DND.js b/modules/home-manager/desktops/hyprland/ags/js/quicksettings/widgets/DND.js deleted file mode 100644 index 3ebc49a..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/quicksettings/widgets/DND.js +++ /dev/null @@ -1,15 +0,0 @@ -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import Notifications from "resource:///com/github/Aylur/ags/service/notifications.js"; -import icons from "../../icons.js"; -import { SimpleToggleButton } from "../ToggleButton.js"; - -export default () => - SimpleToggleButton({ - icon: Widget.Icon({ - icon: Notifications.bind("dnd").transform( - (dnd) => icons.notifications[dnd ? "silent" : "noisy"], - ), - }), - toggle: () => (Notifications.dnd = !Notifications.dnd), - connection: [Notifications, () => Notifications.dnd], - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/quicksettings/widgets/Header.js b/modules/home-manager/desktops/hyprland/ags/js/quicksettings/widgets/Header.js deleted file mode 100644 index 3b6d28b..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/quicksettings/widgets/Header.js +++ /dev/null @@ -1,50 +0,0 @@ -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import Battery from "resource:///com/github/Aylur/ags/service/battery.js"; -import PowerMenu from "../../services/powermenu.js"; -import Lockscreen from "../../services/lockscreen.js"; -import Avatar from "../../misc/Avatar.js"; -import icons from "../../icons.js"; -import { openSettings } from "../../settings/theme.js"; -import { uptime } from "../../variables.js"; -import DND from "./DND.js"; - -export default () => - Widget.Box({ - class_name: "header horizontal", - children: [ - Avatar(), - Widget.Box({ - hpack: "end", - vpack: "center", - hexpand: true, - children: [ - /*Widget.Box({ - class_name: "battery horizontal", - children: [ - Widget.Icon({ icon: Battery.bind("icon_name") }), - Widget.Label({ - label: Battery.bind("percent").transform((p) => `${p}%`), - }), - ], - }),*/ - DND(), - Widget.Label({ - class_name: "uptime", - label: uptime.bind().transform((v) => `up: ${v}`), - }), - Widget.Button({ - on_clicked: openSettings, - child: Widget.Icon(icons.ui.settings), - }), - Widget.Button({ - on_clicked: () => Lockscreen.lockscreen(), - child: Widget.Icon(icons.lock), - }), - Widget.Button({ - on_clicked: () => PowerMenu.action("shutdown"), - child: Widget.Icon(icons.powermenu.shutdown), - }), - ], - }), - ], - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/quicksettings/widgets/Media.js b/modules/home-manager/desktops/hyprland/ags/js/quicksettings/widgets/Media.js deleted file mode 100644 index 699a099..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/quicksettings/widgets/Media.js +++ /dev/null @@ -1,91 +0,0 @@ -import Mpris from "resource:///com/github/Aylur/ags/service/mpris.js"; -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import * as mpris from "../../misc/mpris.js"; -import options from "../../options.js"; - -/** @param {import('types/service/mpris').MprisPlayer} player */ -const Footer = (player) => - Widget.CenterBox({ - class_name: "footer-box", - start_widget: Widget.Box({ - class_name: "position", - children: [ - mpris.PositionLabel(player), - mpris.Slash(player), - mpris.LengthLabel(player), - ], - }), - center_widget: Widget.Box({ - class_name: "controls", - children: [ - mpris.ShuffleButton(player), - mpris.PreviousButton(player), - mpris.PlayPauseButton(player), - mpris.NextButton(player), - mpris.LoopButton(player), - ], - }), - end_widget: mpris.PlayerIcon(player, { - symbolic: false, - hexpand: true, - hpack: "end", - }), - }); - -/** @param {import('types/service/mpris').MprisPlayer} player */ -const TextBox = (player) => - Widget.Box({ - children: [ - mpris.CoverArt(player, { - hpack: "end", - hexpand: false, - }), - Widget.Box({ - hexpand: true, - vertical: true, - class_name: "labels", - children: [ - mpris.TitleLabel(player, { - xalign: 0, - justification: "left", - wrap: true, - }), - mpris.ArtistLabel(player, { - xalign: 0, - justification: "left", - wrap: true, - }), - ], - }), - ], - }); - -/** @param {import('types/service/mpris').MprisPlayer} player */ -const PlayerBox = (player) => - Widget.Box({ - class_name: `player ${player.name}`, - child: mpris.BlurredCoverArt(player, { - hexpand: true, - child: Widget.Box({ - hexpand: true, - vertical: true, - children: [ - TextBox(player), - mpris.PositionSlider(player), - Footer(player), - ], - }), - }), - }); - -export default () => - Widget.Box({ - vertical: true, - class_name: "media vertical", - visible: Mpris.bind("players").transform((p) => p.length > 0), - children: Mpris.bind("players").transform((ps) => - ps - .filter((p) => !options.mpris.black_list.value.includes(p.identity)) - .map(PlayerBox), - ), - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/quicksettings/widgets/MicMute.js b/modules/home-manager/desktops/hyprland/ags/js/quicksettings/widgets/MicMute.js deleted file mode 100644 index 4e797ce..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/quicksettings/widgets/MicMute.js +++ /dev/null @@ -1,19 +0,0 @@ -import Audio from "resource:///com/github/Aylur/ags/service/audio.js"; -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import icons from "../../icons.js"; -import { SimpleToggleButton } from "../ToggleButton.js"; - -export default () => - SimpleToggleButton({ - icon: Widget.Icon().hook( - Audio, - (self) => { - self.icon = Audio.microphone?.is_muted - ? icons.audio.mic.muted - : icons.audio.mic.high; - }, - "microphone-changed", - ), - toggle: () => (Audio.microphone.is_muted = !Audio.microphone.is_muted), - connection: [Audio, () => Audio.microphone?.is_muted || false], - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/quicksettings/widgets/Network.js b/modules/home-manager/desktops/hyprland/ags/js/quicksettings/widgets/Network.js deleted file mode 100644 index 07f1e2c..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/quicksettings/widgets/Network.js +++ /dev/null @@ -1,71 +0,0 @@ -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import Network from "resource:///com/github/Aylur/ags/service/network.js"; -import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; -import icons from "../../icons.js"; -import { Menu, ArrowToggleButton } from "../ToggleButton.js"; -import Applications from "resource:///com/github/Aylur/ags/service/applications.js"; - -export const NetworkToggle = () => - ArrowToggleButton({ - name: "network", - icon: Widget.Icon({ - icon: Network.wifi.bind("icon_name"), - }), - label: Widget.Label({ - truncate: "end", - label: Network.wifi - .bind("ssid") - .transform((ssid) => ssid || "Not Connected"), - }), - connection: [Network, () => Network.wifi.enabled], - deactivate: () => (Network.wifi.enabled = false), - activate: () => { - Network.wifi.enabled = true; - Network.wifi.scan(); - }, - }); - -export const WifiSelection = () => - Menu({ - name: "network", - icon: Widget.Icon({ - icon: Network.wifi.bind("icon_name"), - }), - title: Widget.Label("Wifi Selection"), - content: [ - Widget.Box({ - vertical: true, - setup: (self) => - self.hook( - Network, - () => - (self.children = Network.wifi?.access_points.map((ap) => - Widget.Button({ - on_clicked: () => - Utils.execAsync(`nmcli device wifi connect ${ap.bssid}`), - child: Widget.Box({ - children: [ - Widget.Icon(ap.iconName), - Widget.Label(ap.ssid || ""), - ap.active && - Widget.Icon({ - icon: icons.ui.tick, - hexpand: true, - hpack: "end", - }), - ], - }), - }), - )), - ), - }), - Widget.Separator(), - Widget.Button({ - on_clicked: () => - Applications.query("gnome-control-center")?.[0].launch(), - child: Widget.Box({ - children: [Widget.Icon(icons.ui.settings), Widget.Label("Network")], - }), - }), - ], - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/quicksettings/widgets/Theme.js b/modules/home-manager/desktops/hyprland/ags/js/quicksettings/widgets/Theme.js deleted file mode 100644 index f326a99..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/quicksettings/widgets/Theme.js +++ /dev/null @@ -1,55 +0,0 @@ -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import { ArrowToggleButton, Menu, opened } from "../ToggleButton.js"; -import themes from "../../themes.js"; -import icons from "../../icons.js"; -import options from "../../options.js"; -import { setTheme, openSettings } from "../../settings/theme.js"; - -export const ThemeToggle = () => - ArrowToggleButton({ - name: "theme", - icon: Widget.Label().bind("label", options.theme.icon), - label: Widget.Label().bind("label", options.theme.name), - connection: [opened, () => opened.value === "theme"], - activate: () => opened.setValue("theme"), - activateOnArrow: false, - deactivate: () => {}, - }); - -export const ThemeSelector = () => - Menu({ - name: "theme", - icon: Widget.Label().bind("label", options.theme.icon), - title: Widget.Label("Theme Selector"), - content: [ - ...themes.map(({ name, icon }) => - Widget.Button({ - on_clicked: () => setTheme(name), - child: Widget.Box({ - children: [ - Widget.Label(icon), - Widget.Label(name), - Widget.Icon({ - icon: icons.ui.tick, - hexpand: true, - hpack: "end", - visible: options.theme.name - .bind("value") - .transform((v) => v === name), - }), - ], - }), - }), - ), - Widget.Separator(), - Widget.Button({ - on_clicked: openSettings, - child: Widget.Box({ - children: [ - Widget.Icon(icons.ui.settings), - Widget.Label("Theme Settings"), - ], - }), - }), - ], - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/quicksettings/widgets/Volume.js b/modules/home-manager/desktops/hyprland/ags/js/quicksettings/widgets/Volume.js deleted file mode 100644 index c80a1ff..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/quicksettings/widgets/Volume.js +++ /dev/null @@ -1,158 +0,0 @@ -import Audio from "resource:///com/github/Aylur/ags/service/audio.js"; -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; -import icons from "../../icons.js"; -import FontIcon from "../../misc/FontIcon.js"; -import { getAudioTypeIcon } from "../../utils.js"; -import { Arrow } from "../ToggleButton.js"; -import { Menu } from "../ToggleButton.js"; - -/** @param {'speaker' | 'microphone'=} type */ -const VolumeIndicator = (type = "speaker") => - Widget.Button({ - on_clicked: () => (Audio[type].is_muted = !Audio[type].is_muted), - child: Widget.Icon().hook(Audio[type], (icon) => { - icon.icon = - type === "speaker" - ? getAudioTypeIcon(Audio[type].icon_name || "") - : icons.audio.mic.high; - - icon.tooltip_text = `Volume ${Math.floor(Audio[type].volume * 100)}%`; - }), - }); - -/** @param {'speaker' | 'microphone'=} type */ -const VolumeSlider = (type = "speaker") => - Widget.Slider({ - hexpand: true, - draw_value: false, - on_change: ({ value }) => (Audio[type].volume = value), - setup: (self) => - self.hook(Audio[type], () => { - self.value = Audio[type].volume || 0; - }), - }); - -export const Volume = () => - Widget.Box({ - children: [ - VolumeIndicator("speaker"), - VolumeSlider("speaker"), - Widget.Box({ - vpack: "center", - child: Arrow("sink-selector"), - }), - Widget.Box({ - vpack: "center", - child: Arrow("app-mixer"), - visible: Audio.bind("apps").transform((a) => a.length > 0), - }), - ], - }); - -export const Microhone = () => - Widget.Box({ - class_name: "slider horizontal", - visible: Audio.bind("recorders").transform((a) => a.length > 0), - children: [VolumeIndicator("microphone"), VolumeSlider("microphone")], - }); - -/** @param {import('types/service/audio').Stream} stream */ -const MixerItem = (stream) => - Widget.Box({ - hexpand: true, - class_name: "mixer-item horizontal", - children: [ - Widget.Icon({ - tooltip_text: stream.bind("name").transform((n) => n || ""), - icon: stream.bind("name").transform((n) => { - return Utils.lookUpIcon(n || "") ? n || "" : icons.mpris.fallback; - }), - }), - Widget.Box({ - vertical: true, - children: [ - Widget.Label({ - xalign: 0, - truncate: "end", - label: stream.bind("description").transform((d) => d || ""), - }), - Widget.Slider({ - hexpand: true, - draw_value: false, - value: stream.bind("volume"), - on_change: ({ value }) => (stream.volume = value), - }), - ], - }), - Widget.Label({ - xalign: 1, - label: stream.bind("volume").transform((v) => `${Math.floor(v * 100)}`), - }), - ], - }); - -/** @param {import('types/service/audio').Stream} stream */ -const SinkItem = (stream) => - Widget.Button({ - hexpand: true, - on_clicked: () => (Audio.speaker = stream), - child: Widget.Box({ - children: [ - Widget.Icon({ - icon: getAudioTypeIcon(stream.icon_name || ""), - tooltip_text: stream.icon_name, - }), - Widget.Label( - (stream.description || "").split(" ").slice(0, 4).join(" "), - ), - Widget.Icon({ - icon: icons.ui.tick, - hexpand: true, - hpack: "end", - visible: Audio.speaker - .bind("stream") - .transform((s) => s === stream.stream), - }), - ], - }), - }); - -const SettingsButton = () => - Widget.Button({ - on_clicked: () => Utils.execAsync("pavucontrol"), - hexpand: true, - child: Widget.Box({ - children: [Widget.Icon(icons.ui.settings), Widget.Label("Settings")], - }), - }); - -export const AppMixer = () => - Menu({ - name: "app-mixer", - icon: FontIcon(icons.audio.mixer), - title: Widget.Label("App Mixer"), - content: [ - Widget.Box({ - vertical: true, - children: Audio.bind("apps").transform((a) => a.map(MixerItem)), - }), - Widget.Separator(), - SettingsButton(), - ], - }); - -export const SinkSelector = () => - Menu({ - name: "sink-selector", - icon: Widget.Icon(icons.audio.type.headset), - title: Widget.Label("Sink Selector"), - content: [ - Widget.Box({ - vertical: true, - children: Audio.bind("speakers").transform((a) => a.map(SinkItem)), - }), - Widget.Separator(), - SettingsButton(), - ], - }); diff --git a/modules/home-manager/desktops/hyprland/ags/js/screencorner/ScreenCorners.js b/modules/home-manager/desktops/hyprland/ags/js/screencorner/ScreenCorners.js deleted file mode 100644 index f6ce275..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/screencorner/ScreenCorners.js +++ /dev/null @@ -1,75 +0,0 @@ -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import Gtk from "gi://Gtk"; -import options from "../options.js"; - -/** @param {'topleft' | 'topright' | 'bottomleft' | 'bottomright'} place */ -const Corner = (place) => - Widget.DrawingArea({ - class_name: "corner", - hexpand: true, - vexpand: true, - hpack: place.includes("left") ? "start" : "end", - vpack: place.includes("top") ? "start" : "end", - setup: (self) => - self - .hook(options.radii, () => { - const r = options.radii.value * 2; - self.set_size_request(r, r); - }) - .connect("draw", (self, cr) => { - const context = self.get_style_context(); - const c = context.get_property( - "background-color", - Gtk.StateFlags.NORMAL, - ); - const r = context.get_property( - "border-radius", - Gtk.StateFlags.NORMAL, - ); - - switch (place) { - case "topleft": - cr.arc(r, r, r, Math.PI, (3 * Math.PI) / 2); - cr.lineTo(0, 0); - break; - - case "topright": - cr.arc(0, r, r, (3 * Math.PI) / 2, 2 * Math.PI); - cr.lineTo(r, 0); - break; - - case "bottomleft": - cr.arc(r, 0, r, Math.PI / 2, Math.PI); - cr.lineTo(0, r); - break; - - case "bottomright": - cr.arc(0, 0, r, 0, Math.PI / 2); - cr.lineTo(r, r); - break; - } - - cr.closePath(); - cr.setSourceRGBA(c.red, c.green, c.blue, c.alpha); - cr.fill(); - }), - }); - -/** @type {Array<'topleft' | 'topright' | 'bottomleft' | 'bottomright'>} */ -const places = ["topleft", "topright", "bottomleft", "bottomright"]; - -/** @param {number} monitor */ -export default (monitor) => - places.map((place) => - Widget.Window({ - name: `corner${monitor}${place}`, - monitor, - class_name: "corner", - anchor: [ - place.includes("top") ? "top" : "bottom", - place.includes("right") ? "right" : "left", - ], - visible: options.desktop.screen_corners.bind("value"), - child: Corner(place), - }), - ); diff --git a/modules/home-manager/desktops/hyprland/ags/js/services/asusctl.js b/modules/home-manager/desktops/hyprland/ags/js/services/asusctl.js deleted file mode 100644 index a1ecca6..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/services/asusctl.js +++ /dev/null @@ -1,70 +0,0 @@ -import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; -import Service from "resource:///com/github/Aylur/ags/service.js"; - -class Asusctl extends Service { - static { - Service.register( - this, - {}, - { - profile: ["string", "r"], - mode: ["string", "r"], - }, - ); - } - - profiles = /** @type {const} */ (["Performance", "Balanced", "Quiet"]); - #profile = "Balanced"; - #mode = "Hyprid"; - - nextProfile() { - Utils.execAsync("asusctl profile -n") - .then(() => { - this.#profile = Utils.exec("asusctl profile -p").split(" ")[3]; - this.changed("profile"); - }) - .catch(console.error); - } - - /** @param {'Performance' | 'Balanced' | 'Quiet'} prof */ - setProfile(prof) { - Utils.execAsync(`asusctl profile --profile-set ${prof}`) - .then(() => { - this.#profile = prof; - this.changed("profile"); - }) - .catch(console.error); - } - - nextMode() { - Utils.execAsync( - `supergfxctl -m ${this.#mode === "Hybrid" ? "Integrated" : "Hybrid"}`, - ) - .then(() => { - this.#mode = Utils.exec("supergfxctl -g"); - this.changed("profile"); - }) - .catch(console.error); - } - - constructor() { - super(); - - if (Utils.exec("which asusctl")) { - this.available = true; - this.#profile = Utils.exec("asusctl profile -p").split(" ")[3]; - Utils.execAsync("supergfxctl -g").then((mode) => (this.#mode = mode)); - } else { - this.available = false; - } - } - - get profile() { - return this.#profile; - } - get mode() { - return this.#mode; - } -} - -export default new Asusctl(); diff --git a/modules/home-manager/desktops/hyprland/ags/js/services/brightness.js b/modules/home-manager/desktops/hyprland/ags/js/services/brightness.js deleted file mode 100644 index 9f67b83..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/services/brightness.js +++ /dev/null @@ -1,77 +0,0 @@ -import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; -import Service from "resource:///com/github/Aylur/ags/service.js"; -import options from "../options.js"; -import { dependencies } from "../utils.js"; - -const KBD = options.brightnessctlKBD; - -class Brightness extends Service { - static { - Service.register( - this, - {}, - { - screen: ["float", "rw"], - kbd: ["int", "rw"], - }, - ); - } - - #kbd = 0; - #kbdMax = 3; - #screen = 0; - - get kbd() { - return this.#kbd; - } - get screen() { - return this.#screen; - } - - set kbd(value) { - if (!dependencies(["brightnessctl"])) return; - - if (value < 0 || value > this.#kbdMax) return; - - Utils.execAsync(`brightnessctl -d ${KBD} s ${value} -q`) - .then(() => { - this.#kbd = value; - this.changed("kbd"); - }) - .catch(console.error); - } - - set screen(percent) { - if (!dependencies(["gbmonctl"])) return; - - if (percent < 0) percent = 0; - - if (percent > 1) percent = 1; - - Utils.execAsync( - `gbmonctl --prop brightness -val ${Math.min( - Math.max(Math.floor(percent * 100), 0), - 100, - )}`, - ) - .then(() => { - this.#screen = percent; - this.changed("screen"); - }) - .catch(console.error); - } - - constructor() { - super(); - - if (dependencies(["brightnessctl"])) { - this.#kbd = Number(Utils.exec(`brightnessctl -d ${KBD} g`)); - this.#kbdMax = Number(Utils.exec(`brightnessctl -d ${KBD} m`)); - this.#screen = - Number(Utils.exec("brightnessctl g")) / - Number(Utils.exec("brightnessctl m")); - } - } -} - -export default new Brightness(); diff --git a/modules/home-manager/desktops/hyprland/ags/js/services/colorpicker.js b/modules/home-manager/desktops/hyprland/ags/js/services/colorpicker.js deleted file mode 100644 index 3ad33e7..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/services/colorpicker.js +++ /dev/null @@ -1,73 +0,0 @@ -import { Variable } from "resource:///com/github/Aylur/ags/variable.js"; -import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; -import Service from "resource:///com/github/Aylur/ags/service.js"; -import { dependencies } from "../utils.js"; -import icons from "../icons.js"; - -const COLORS_CACHE = Utils.CACHE_DIR + "/colorpicker.json"; - -class Colors extends Service { - static { - Service.register( - this, - {}, - { - colors: ["jsobject"], - }, - ); - } - - /** @type {Variable} */ - #colors = new Variable([]); - get colors() { - return this.#colors.value; - } - - #notifID = 0; - - constructor() { - super(); - - this.#colors.connect("changed", () => this.changed("colors")); - - Utils.readFileAsync(COLORS_CACHE) - .then((out) => this.#colors.setValue(JSON.parse(out || "[]"))) - .catch(() => print("no colorpicker cache found")); - } - - /** @param {string} color */ - wlCopy(color) { - Utils.execAsync(["wl-copy", color]).catch((err) => console.error(err)); - } - - async pick() { - if (!dependencies(["hyprpicker"])) return; - - const color = await Utils.execAsync("hyprpicker"); - if (!color) return; - - this.wlCopy(color); - const list = this.#colors.value; - if (!list.includes(color)) { - list.push(color); - if (list.length > 10) list.shift(); - - this.#colors.value = list; - Utils.writeFile(JSON.stringify(list, null, 2), COLORS_CACHE).catch( - (err) => console.error(err), - ); - } - - const n = await Utils.notify({ - id: this.#notifID, - iconName: icons.ui.colorpicker, - summary: color, - actions: { - Copy: () => this.wlCopy(color), - }, - }); - this.#notifID = n.id; - } -} - -export default new Colors(); diff --git a/modules/home-manager/desktops/hyprland/ags/js/services/lockscreen.js b/modules/home-manager/desktops/hyprland/ags/js/services/lockscreen.js deleted file mode 100644 index ca3c603..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/services/lockscreen.js +++ /dev/null @@ -1,30 +0,0 @@ -import Service from "resource:///com/github/Aylur/ags/service.js"; -import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; -import App from "resource:///com/github/Aylur/ags/app.js"; -const authpy = App.configDir + "/js/lockscreen/auth.py"; - -class Lockscreen extends Service { - static { - Service.register(this, { - lock: ["boolean"], - authenticating: ["boolean"], - }); - } - - lockscreen() { - this.emit("lock", true); - } - - /** @param {string} password */ - auth(password) { - this.emit("authenticating", true); - Utils.execAsync([authpy, password]) - .then((out) => { - this.emit("lock", out !== "True"); - this.emit("authenticating", false); - }) - .catch((err) => console.error(err)); - } -} - -export default new Lockscreen(); diff --git a/modules/home-manager/desktops/hyprland/ags/js/services/onScreenIndicator.js b/modules/home-manager/desktops/hyprland/ags/js/services/onScreenIndicator.js deleted file mode 100644 index f1d16ad..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/services/onScreenIndicator.js +++ /dev/null @@ -1,58 +0,0 @@ -import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; -import Service from "resource:///com/github/Aylur/ags/service.js"; -import Audio from "resource:///com/github/Aylur/ags/service/audio.js"; -import icons from "../icons.js"; -import { getAudioTypeIcon } from "../utils.js"; -import Brightness from "./brightness.js"; - -class Indicator extends Service { - static { - Service.register(this, { - popup: ["double", "string"], - }); - } - - #delay = 1500; - #count = 0; - - /** - * @param {number} value - 0 < v < 1 - * @param {string} icon - */ - popup(value, icon) { - this.emit("popup", value, icon); - this.#count++; - Utils.timeout(this.#delay, () => { - this.#count--; - - if (this.#count === 0) this.emit("popup", -1, icon); - }); - } - - speaker() { - this.popup( - Audio.speaker?.volume || 0, - getAudioTypeIcon(Audio.speaker?.icon_name || ""), - ); - } - - display() { - // brightness is async, so lets wait a bit - Utils.timeout(10, () => - this.popup(Brightness.screen, icons.brightness.screen), - ); - } - - kbd() { - // brightness is async, so lets wait a bit - Utils.timeout(10, () => - this.popup((Brightness.kbd * 33 + 1) / 100, icons.brightness.keyboard), - ); - } - - connect(event = "popup", callback) { - return super.connect(event, callback); - } -} - -export default new Indicator(); diff --git a/modules/home-manager/desktops/hyprland/ags/js/services/powermenu.js b/modules/home-manager/desktops/hyprland/ags/js/services/powermenu.js deleted file mode 100644 index c4abd82..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/services/powermenu.js +++ /dev/null @@ -1,43 +0,0 @@ -import App from "resource:///com/github/Aylur/ags/app.js"; -import Service from "resource:///com/github/Aylur/ags/service.js"; - -class PowerMenu extends Service { - static { - Service.register( - this, - {}, - { - title: ["string"], - cmd: ["string"], - }, - ); - } - - #title = ""; - #cmd = ""; - - get title() { - return this.#title; - } - get cmd() { - return this.#cmd; - } - - /** @param {'sleep' | 'reboot' | 'logout' | 'shutdown'} action */ - action(action) { - [this.#cmd, this.#title] = { - sleep: ["systemctl suspend", "Sleep"], - reboot: ["systemctl reboot", "Reboot"], - logout: ["pkill Hyprland", "Log Out"], - shutdown: ["shutdown now", "Shutdown"], - }[action]; - - this.notify("cmd"); - this.notify("title"); - this.emit("changed"); - App.closeWindow("powermenu"); - App.openWindow("verification"); - } -} - -export default new PowerMenu(); diff --git a/modules/home-manager/desktops/hyprland/ags/js/services/screenrecord.js b/modules/home-manager/desktops/hyprland/ags/js/services/screenrecord.js deleted file mode 100644 index 3421f4c..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/services/screenrecord.js +++ /dev/null @@ -1,112 +0,0 @@ -import Service from "resource:///com/github/Aylur/ags/service.js"; -import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; -import App from "resource:///com/github/Aylur/ags/app.js"; -import GLib from "gi://GLib"; -import { dependencies } from "../utils.js"; - -const now = () => GLib.DateTime.new_now_local().format("%Y-%m-%d_%H-%M-%S"); - -class Recorder extends Service { - static { - Service.register( - this, - {}, - { - timer: ["int"], - recording: ["boolean"], - }, - ); - } - - #path = GLib.get_home_dir() + "/Videos/Screencasting"; - #file = ""; - #interval = 0; - - recording = false; - timer = 0; - - async start() { - if (!dependencies(["slurp", "wf-recorder"])) return; - - if (this.recording) return; - - const area = await Utils.execAsync("slurp"); - Utils.ensureDirectory(this.#path); - this.#file = `${this.#path}/${now()}.mp4`; - Utils.execAsync(["wf-recorder", "-g", area, "-f", this.#file]); - this.recording = true; - this.changed("recording"); - - this.timer = 0; - this.#interval = Utils.interval(1000, () => { - this.changed("timer"); - this.timer++; - }); - } - - async stop() { - if (!dependencies(["notify-send"])) return; - - if (!this.recording) return; - - Utils.execAsync("killall -INT wf-recorder"); - this.recording = false; - this.changed("recording"); - GLib.source_remove(this.#interval); - - const res = await Utils.execAsync([ - "notify-send", - "-A", - "files=Show in Files", - "-A", - "view=View", - "-i", - "video-x-generic-symbolic", - "Screenrecord", - this.#file, - ]); - - if (res === "files") Utils.execAsync("xdg-open " + this.#path); - - if (res === "view") Utils.execAsync("xdg-open " + this.#file); - } - - async screenshot(full = false) { - if (!dependencies(["slurp", "wayshot"])) return; - - const path = GLib.get_home_dir() + "/Pictures/Screenshots"; - const file = `${path}/${now()}.png`; - Utils.ensureDirectory(path); - - await Utils.execAsync( - ["wayshot", "-f", file].concat( - full ? [] : ["-s", await Utils.execAsync("slurp")], - ), - ); - - Utils.execAsync(["bash", "-c", `wl-copy < ${file}`]); - - const res = await Utils.execAsync([ - "notify-send", - "-A", - "files=Show in Files", - "-A", - "view=View", - "-A", - "edit=Edit", - "-i", - file, - "Screenshot", - file, - ]); - if (res === "files") Utils.execAsync("xdg-open " + path); - - if (res === "view") Utils.execAsync("xdg-open " + file); - - if (res === "edit") Utils.execAsync(["swappy", "-f", file]); - - App.closeWindow("dashboard"); - } -} - -export default new Recorder(); diff --git a/modules/home-manager/desktops/hyprland/ags/js/settings/SettingsDialog.js b/modules/home-manager/desktops/hyprland/ags/js/settings/SettingsDialog.js deleted file mode 100644 index 6f9b4c1..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/settings/SettingsDialog.js +++ /dev/null @@ -1,297 +0,0 @@ -import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; -import App from "resource:///com/github/Aylur/ags/app.js"; -import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import RegularWindow from "../misc/RegularWindow.js"; -import Variable from "resource:///com/github/Aylur/ags/variable.js"; -import icons from "../icons.js"; -import { getOptions, getValues } from "./option.js"; -import options from "../options.js"; - -const optionsList = getOptions(); -const categories = Array.from( - new Set(optionsList.map((opt) => opt.category)), -).filter((category) => category !== "exclude"); - -const currentPage = Variable(categories[0]); -const search = Variable(""); -const showSearch = Variable(false); -showSearch.connect("changed", ({ value }) => { - if (!value) search.value = ""; -}); - -/** @param {import('./option.js').Opt} opt */ -const EnumSetter = (opt) => { - const lbl = Widget.Label().bind("label", opt); - const step = (dir = 1) => { - const i = opt.enums.findIndex((i) => i === lbl.label); - opt.setValue( - dir > 0 - ? i + dir > opt.enums.length - 1 - ? opt.enums[0] - : opt.enums[i + dir] - : i + dir < 0 - ? opt.enums[opt.enums.length - 1] - : opt.enums[i + dir], - true, - ); - }; - const next = Widget.Button({ - child: Widget.Icon(icons.ui.arrow.right), - on_clicked: () => step(+1), - }); - const prev = Widget.Button({ - child: Widget.Icon(icons.ui.arrow.left), - on_clicked: () => step(-1), - }); - return Widget.Box({ - class_name: "enum-setter", - children: [prev, lbl, next], - }); -}; - -/** @param {import('./option.js').Opt} opt */ -const Setter = (opt) => { - switch (opt.type) { - case "number": - return Widget.SpinButton({ - setup(self) { - self.set_range(0, 1000); - self.set_increments(1, 5); - self.on("value-changed", () => opt.setValue(self.value, true)); - self.hook(opt, () => (self.value = opt.value)); - }, - }); - case "float": - case "object": - return Widget.Entry({ - on_accept: (self) => opt.setValue(JSON.parse(self.text || ""), true), - setup: (self) => - self.hook(opt, () => (self.text = JSON.stringify(opt.value))), - }); - case "string": - return Widget.Entry({ - on_accept: (self) => opt.setValue(self.text, true), - setup: (self) => self.hook(opt, () => (self.text = opt.value)), - }); - case "enum": - return EnumSetter(opt); - case "boolean": - return Widget.Switch() - .on("notify::active", (self) => opt.setValue(self.active, true)) - .hook(opt, (self) => (self.active = opt.value)); - - case "img": - return Widget.FileChooserButton().on("selection-changed", (self) => { - opt.setValue(self.get_uri()?.replace("file://", ""), true); - }); - - case "font": - return Widget.FontButton({ - show_size: false, - use_size: false, - setup: (self) => - self - .on("notify::font", ({ font }) => opt.setValue(font, true)) - .hook(opt, () => (self.font = opt.value)), - }); - default: - return Widget.Label({ - label: "no setter with type " + opt.type, - }); - } -}; - -/** @param {import('./option.js').Opt} opt */ -const Row = (opt) => - Widget.Box({ - class_name: "row", - attribute: opt, - children: [ - Widget.Box({ - vertical: true, - vpack: "center", - children: [ - opt.title && - Widget.Label({ - xalign: 0, - class_name: "summary", - label: opt.title, - }), - Widget.Label({ - xalign: 0, - class_name: "id", - label: `id: "${opt.id}"`, - }), - ], - }), - Widget.Box({ hexpand: true }), - Widget.Box({ - vpack: "center", - vertical: true, - children: [ - Widget.Box({ - hpack: "end", - child: Setter(opt), - }), - opt.note && - Widget.Label({ - xalign: 1, - class_name: "note", - label: opt.note, - }), - ], - }), - ], - }); - -/** @param {string} category */ -const Page = (category) => - Widget.Scrollable({ - vexpand: true, - class_name: "page", - child: Widget.Box({ - class_name: "page-content vertical", - vertical: true, - setup: (self) => - self.hook(search, () => { - for (const child of self.children) { - child.visible = - child.attribute.id.includes(search.value) || - child.attribute.title.includes(search.value) || - child.attribute.note.includes(search.value); - } - }), - children: optionsList - .filter((opt) => opt.category.includes(category)) - .map(Row), - }), - }); - -const sidebar = Widget.Revealer({ - reveal_child: search.bind().transform((v) => !v), - transition: "slide_right", - child: Widget.Box({ - hexpand: false, - vertical: true, - children: [ - Widget.Box({ - class_name: "sidebar-header", - children: [ - Widget.Button({ - hexpand: true, - label: icons.dialog.Search + " Search", - on_clicked: () => (showSearch.value = !showSearch.value), - }), - Widget.Button({ - hpack: "end", - child: Widget.Icon(icons.ui.info), - on_clicked: () => App.toggleWindow("about"), - }), - ], - }), - Widget.Scrollable({ - vexpand: true, - hscroll: "never", - child: Widget.Box({ - class_name: "sidebar-box vertical", - vertical: true, - children: [ - ...categories.map((name) => - Widget.Button({ - label: (icons.dialog[name] || "") + " " + name, - xalign: 0, - class_name: currentPage - .bind() - .transform((v) => `${v === name ? "active" : ""}`), - on_clicked: () => currentPage.setValue(name), - }), - ), - ], - }), - }), - Widget.Box({ - class_name: "sidebar-footer", - child: Widget.Button({ - class_name: "copy", - child: Widget.Label({ - label: " Save", - xalign: 0, - }), - hexpand: true, - on_clicked: () => { - Utils.execAsync(["wl-copy", getValues()]); - Utils.execAsync([ - "notify-send", - "-i", - "preferences-desktop-theme-symbolic", - "Theme copied to clipboard", - 'To save it permanently, make a new theme in themes.js', - ]); - }, - }), - }), - ], - }), -}); - -const searchEntry = Widget.Revealer({ - transition: "slide_down", - reveal_child: showSearch.bind(), - transition_duration: options.transition.bind("value"), - child: Widget.Entry({ - setup: (self) => - self.hook(showSearch, () => { - if (!showSearch.value) self.text = ""; - - if (showSearch.value) self.grab_focus(); - }), - hexpand: true, - class_name: "search", - placeholder_text: "Search Options", - secondary_icon_name: icons.apps.search, - on_change: ({ text }) => (search.value = text || ""), - }), -}); - -const categoriesStack = Widget.Stack({ - transition: "slide_left_right", - children: categories.reduce((obj, name) => { - obj[name] = Page(name); - return obj; - }, {}), - shown: currentPage.bind(), - visible: search.bind().transform((v) => !v), -}); - -const searchPage = Widget.Box({ - visible: search.bind().transform((v) => !!v), - child: Page(""), -}); - -export default RegularWindow({ - name: "settings-dialog", - title: "Settings", - setup: (win) => - win - .on("delete-event", () => { - win.hide(); - return true; - }) - .on("key-press-event", (_, event) => { - if (event.get_keyval()[1] === imports.gi.Gdk.KEY_Escape) { - showSearch.setValue(false); - search.setValue(""); - } - }) - .set_default_size(800, 500), - - child: Widget.Box({ - children: [ - sidebar, - Widget.Box({ - vertical: true, - children: [searchEntry, categoriesStack, searchPage], - }), - ], - }), -}); diff --git a/modules/home-manager/desktops/hyprland/ags/js/settings/globals.js b/modules/home-manager/desktops/hyprland/ags/js/settings/globals.js deleted file mode 100644 index 33c44d9..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/settings/globals.js +++ /dev/null @@ -1,40 +0,0 @@ -import Mpris from "resource:///com/github/Aylur/ags/service/mpris.js"; - -export async function globals() { - try { - globalThis.options = (await import("../options.js")).default; - globalThis.iconBrowser = (await import("../misc/IconBrowser.js")).default; - globalThis.app = ( - await import("resource:///com/github/Aylur/ags/app.js") - ).default; - globalThis.audio = ( - await import("resource:///com/github/Aylur/ags/service/audio.js") - ).default; - globalThis.recorder = (await import("../services/screenrecord.js")).default; - globalThis.brightness = (await import("../services/brightness.js")).default; - globalThis.indicator = ( - await import("../services/onScreenIndicator.js") - ).default; - globalThis.app = ( - await import("resource:///com/github/Aylur/ags/app.js") - ).default; - - Mpris.players.forEach((player) => { - player.connect("changed", (player) => { - globalThis.mpris = player || Mpris.players[0]; - }); - }); - - Mpris.connect("player-added", (mpris, bus) => { - mpris.getPlayer(bus)?.connect("changed", (player) => { - globalThis.mpris = player || Mpris.players[0]; - }); - }); - - Mpris.connect("player-closed", () => { - globalThis.mpris = Mpris.players[0]; - }); - } catch (error) { - logError(error); - } -} diff --git a/modules/home-manager/desktops/hyprland/ags/js/settings/hyprland.js b/modules/home-manager/desktops/hyprland/ags/js/settings/hyprland.js deleted file mode 100644 index bf838bc..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/settings/hyprland.js +++ /dev/null @@ -1,69 +0,0 @@ -import App from "resource:///com/github/Aylur/ags/app.js"; -import Hyprland from "resource:///com/github/Aylur/ags/service/hyprland.js"; -import options from "../options.js"; -import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; - -const noIgnorealpha = ["verification", "powermenu", "lockscreen"]; - -/** @param {Array} batch */ -function sendBatch(batch) { - const cmd = batch - .filter((x) => !!x) - .map((x) => `keyword ${x}`) - .join("; "); - - Hyprland.sendMessage(`[[BATCH]]/${cmd}`); -} - -/** @param {string} scss */ -function getColor(scss) { - if (scss.includes("#")) return scss.replace("#", ""); - - if (scss.includes("$")) { - const opt = options - .list() - .find((opt) => opt.scss === scss.replace("$", "")); - return opt?.value.replace("#", "") || "ff0000"; - } -} - -export function hyprlandInit() { - sendBatch( - App.windows.flatMap(({ name }) => [ - `layerrule blur, ${name}`, - noIgnorealpha.some((skip) => name?.includes(skip)) - ? "" - : `layerrule ignorealpha 0.3, ${name}`, - ]), - ); -} - -export async function setupHyprland() { - /*Hyprland.event("activewindowv2", async (addr) => { - const client = Hyprland.getClient(addr); - if (!client.pinned || !client.floating) return; - const x = client.at[0]; - console.log( - await Utils.execAsync(`hyprctl dispatch moveactive exact ${x} 80`), - ); - });*/ - - const wm_gaps = Math.floor( - options.hypr.wm_gaps_multiplier.value * options.spacing.value, - ); - const border_width = options.border.width.value; - const radii = options.radii.value; - const drop_shadow = options.desktop.drop_shadow.value; - const inactive_border = options.hypr.inactive_border.value; - const accent = getColor(options.theme.accent.accent.value); - - sendBatch([ - `general:border_size ${border_width}`, - `general:gaps_out ${wm_gaps}`, - `general:gaps_in ${Math.floor(wm_gaps / 2)}`, - `general:col.active_border rgba(${accent}ff)`, - `general:col.inactive_border ${inactive_border}`, - `decoration:rounding ${radii}`, - `decoration:drop_shadow ${drop_shadow ? "yes" : "no"}`, - ]); -} diff --git a/modules/home-manager/desktops/hyprland/ags/js/settings/option.js b/modules/home-manager/desktops/hyprland/ags/js/settings/option.js deleted file mode 100644 index cd1e945..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/settings/option.js +++ /dev/null @@ -1,198 +0,0 @@ -import { - CACHE_DIR, - readFile, - writeFile, -} from "resource:///com/github/Aylur/ags/utils.js"; -import { exec } from "resource:///com/github/Aylur/ags/utils.js"; -import options from "../options.js"; -import Service from "resource:///com/github/Aylur/ags/service.js"; -import { reloadScss } from "./scss.js"; -import { setupHyprland } from "./hyprland.js"; -const CACHE_FILE = CACHE_DIR + "/options.json"; - -/** object that holds the overriedden values */ -let cacheObj = JSON.parse(readFile(CACHE_FILE) || "{}"); - -/** - * @template T - * @typedef {Object} OptionConfig - * @property {string=} scss - name of scss variable set to "exclude" to not include it in the generated scss file - * @property {string=} unit - scss unit on numbers, default is "px" - * @property {string=} title - * @property {string=} note - * @property {string=} category - * @property {boolean=} noReload - don't reload css & hyprland on change - * @property {boolean=} persist - ignore reset call - * @property {'object' | 'string' | 'img' | 'number' | 'float' | 'font' | 'enum' =} type - * @property {Array =} enums - * @property {(value: T) => any=} format - * @property {(value: T) => any=} scssFormat - */ - -/** @template T */ -export class Opt extends Service { - static { - Service.register( - this, - {}, - { - value: ["jsobject"], - }, - ); - } - - #value; - #scss = ""; - unit = "px"; - noReload = false; - persist = false; - id = ""; - title = ""; - note = ""; - type = ""; - category = ""; - - /** @type {Array} */ - enums = []; - - /** @type {(v: T) => any} */ - format = (v) => v; - - /** @type {(v: T) => any} */ - scssFormat = (v) => v; - - /** - * @param {T} value - * @param {OptionConfig =} config - */ - constructor(value, config) { - super(); - this.#value = value; - this.defaultValue = value; - this.type = typeof value; - - if (config) Object.keys(config).forEach((c) => (this[c] = config[c])); - - import("../options.js").then(this.#init.bind(this)); - } - - set scss(scss) { - this.#scss = scss; - } - get scss() { - return this.#scss || this.id.split(".").join("-").split("_").join("-"); - } - - #init() { - getOptions(); // sets the ids as a side effect - - if (cacheObj[this.id] !== undefined) this.setValue(cacheObj[this.id]); - - const words = this.id - .split(".") - .flatMap((w) => w.split("_")) - .map((word) => word.charAt(0).toUpperCase() + word.slice(1)); - - this.title ||= words.join(" "); - this.category ||= words.length === 1 ? "General" : words.at(0) || "General"; - - this.connect("changed", () => { - cacheObj[this.id] = this.value; - writeFile(JSON.stringify(cacheObj, null, 2), CACHE_FILE); - }); - } - - get value() { - return this.#value; - } - set value(value) { - this.setValue(value); - } - - /** @param {T} value */ - setValue(value, reload = false) { - if (typeof value !== typeof this.defaultValue) { - console.error( - Error( - `WrongType: Option "${this.id}" can't be set to ${value}, ` + - `expected "${typeof this.defaultValue}", but got "${typeof value}"`, - ), - ); - - return; - } - - if (this.value !== value) { - this.#value = this.format(value); - this.changed("value"); - - if (reload && !this.noReload) { - reloadScss(); - setupHyprland(); - } - } - } - - reset(reload = false) { - if (!this.persist) this.setValue(this.defaultValue, reload); - } -} - -/** - * @template T - * @param {T} value - * @param {OptionConfig =} config - * @returns {Opt} - */ -export function Option(value, config) { - return new Opt(value, config); -} - -/** @returns {Array>} */ -export function getOptions(object = options, path = "") { - return Object.keys(object).flatMap((key) => { - /** @type Option */ - const obj = object[key]; - const id = path ? path + "." + key : key; - - if (obj instanceof Opt) { - obj.id = id; - return obj; - } - - if (typeof obj === "object") return getOptions(obj, id); - - return []; - }); -} - -export function resetOptions() { - exec(`rm -rf ${CACHE_FILE}`); - cacheObj = {}; - getOptions().forEach((opt) => opt.reset()); -} - -export function getValues() { - const obj = {}; - for (const opt of getOptions()) { - if (opt.category !== "exclude") obj[opt.id] = opt.value; - } - - return JSON.stringify(obj, null, 2); -} - -/** @param {string | object} config */ -export function apply(config) { - const options = getOptions(); - const settings = typeof config === "string" ? JSON.parse(config) : config; - - for (const id of Object.keys(settings)) { - const opt = options.find((opt) => opt.id === id); - if (!opt) { - print(`No option with id: "${id}"`); - continue; - } - - opt.setValue(settings[id]); - } -} diff --git a/modules/home-manager/desktops/hyprland/ags/js/settings/scss.js b/modules/home-manager/desktops/hyprland/ags/js/settings/scss.js deleted file mode 100644 index be4f48e..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/settings/scss.js +++ /dev/null @@ -1,62 +0,0 @@ -import App from "resource:///com/github/Aylur/ags/app.js"; -import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; -import { getOptions } from "./option.js"; - -export function scssWatcher() { - return Utils.subprocess( - [ - "inotifywait", - "--recursive", - "--event", - "create,modify", - "-m", - App.configDir + "/scss", - ], - reloadScss, - () => print("missing dependancy for css hotreload: inotify-tools"), - ); -} - -/** - * generate an scss file that makes every option available as a variable - * based on the passed scss parameter or the path in the object - * - * e.g - * options.bar.style.value => $bar-style - */ -export async function reloadScss() { - const opts = getOptions(); - const vars = opts.map((opt) => { - if (opt.scss === "exclude") return ""; - - const unit = typeof opt.value === "number" ? opt.unit : ""; - const value = opt.scssFormat ? opt.scssFormat(opt.value) : opt.value; - return `$${opt.scss}: ${value}${unit};`; - }); - - const bar_style = opts.find((opt) => opt.id === "bar.style")?.value || ""; - const additional = - bar_style === "normal" - ? "//" - : ` - window#quicksettings .window-content { - margin-right: $wm-gaps; - } - `; - - try { - const tmp = "/tmp/ags/scss"; - Utils.ensureDirectory(tmp); - await Utils.writeFile(vars.join("\n"), `${tmp}/options.scss`); - await Utils.writeFile(additional, `${tmp}/additional.scss`); - await Utils.execAsync( - `sassc ${App.configDir}/scss/main.scss ${tmp}/style.css`, - ); - App.resetCss(); - App.applyCss(`${tmp}/style.css`); - } catch (error) { - if (error instanceof Error) console.error(error.message); - - if (typeof error === "string") console.error(error); - } -} diff --git a/modules/home-manager/desktops/hyprland/ags/js/settings/setup.js b/modules/home-manager/desktops/hyprland/ags/js/settings/setup.js deleted file mode 100644 index 8c8efe6..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/settings/setup.js +++ /dev/null @@ -1,126 +0,0 @@ -import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; -import Battery from "resource:///com/github/Aylur/ags/service/battery.js"; -import Notifications from "resource:///com/github/Aylur/ags/service/notifications.js"; -import options from "../options.js"; -import icons from "../icons.js"; -import { reloadScss } from "./scss.js"; -import { wallpaper } from "./wallpaper.js"; -import { hyprlandInit, setupHyprland } from "./hyprland.js"; -import { globals } from "./globals.js"; -import { showAbout } from "../about/about.js"; -import Gtk from "gi://Gtk?version=3.0"; - -export function init() { - notificationBlacklist(); - warnOnLowBattery(); - globals(); - tmux(); - kitty(); - gsettigsColorScheme(); - gtkFontSettings(); - dependandOptions(); - - reloadScss(); - hyprlandInit(); - setupHyprland(); - wallpaper(); - showAbout(); -} - -function dependandOptions() { - options.bar.style.connect("changed", ({ value }) => { - if (value !== "normal") - options.desktop.screen_corners.setValue(false, true); - }); -} - -function kitty() { - if (!Utils.exec("which kitty")) return; - console.log("kitty"); - options.theme.scheme.connect("changed", ({ value }) => - Utils.execAsync( - `kitty +kitten themes --reload-in=all --config-file-name /home/theaninova/.config/kitty/current-colors.conf Catppuccin-${ - value === "light" ? "Latte" : "Frappe" - }`, - ), - ); -} - -function tmux() { - if (!Utils.exec("which tmux")) return; - - /** @param {string} scss */ - function getColor(scss) { - if (scss.includes("#")) return scss; - - if (scss.includes("$")) { - const opt = options - .list() - .find((opt) => opt.scss === scss.replace("$", "")); - return opt?.value; - } - } - - options.theme.accent.accent.connect("changed", ({ value }) => - Utils.execAsync(`tmux set @main_accent ${getColor(value)}`).catch((err) => - console.error(err.message), - ), - ); -} - -function gsettigsColorScheme() { - if (!Utils.exec("which gsettings")) return; - - options.theme.scheme.connect("changed", ({ value }) => { - const gsettings = "gsettings set org.gnome.desktop.interface color-scheme"; - Utils.execAsync(`${gsettings} "prefer-${value}"`).catch((err) => - console.error(err.message), - ); - }); -} - -function gtkFontSettings() { - const settings = Gtk.Settings.get_default(); - if (!settings) { - console.error(Error("Gtk.Settings unavailable")); - return; - } - - const callback = () => { - const { size, font } = options.font; - settings.gtk_font_name = `${font.value} ${size.value}`; - }; - - options.font.font.connect("notify::value", callback); - options.font.size.connect("notify::value", callback); -} - -function notificationBlacklist() { - Notifications.connect("notified", (_, id) => { - const n = Notifications.getNotification(id); - options.notifications.black_list.value.forEach((item) => { - if (n?.app_name.includes(item) || n?.app_entry?.includes(item)) n.close(); - }); - }); -} - -function warnOnLowBattery() { - Battery.connect("notify::percent", () => { - const low = options.battery.low.value; - if ( - Battery.percent !== low || - Battery.percent !== low / 2 || - !Battery.charging - ) - return; - - Utils.execAsync([ - "notify-send", - `${Battery.percent}% Battery Percentage`, - "-i", - icons.battery.warning, - "-u", - "critical", - ]); - }); -} diff --git a/modules/home-manager/desktops/hyprland/ags/js/settings/theme.js b/modules/home-manager/desktops/hyprland/ags/js/settings/theme.js deleted file mode 100644 index 7b54cf4..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/settings/theme.js +++ /dev/null @@ -1,68 +0,0 @@ -import App from "resource:///com/github/Aylur/ags/app.js"; -import options from "../options.js"; -import themes from "../themes.js"; -import { reloadScss } from "./scss.js"; -import { setupHyprland } from "./hyprland.js"; -import { wallpaper } from "./wallpaper.js"; - -/** @param {string} name */ -export function setTheme(name) { - options.reset(); - const theme = themes.find((t) => t.name === name); - if (!theme) return print("No theme named " + name); - - options.apply(theme.options); - reloadScss(); - setupHyprland(); - wallpaper(); -} - -export const WP = App.configDir + "/assets/"; - -export const lightColors = { - "theme.scheme": "light", - "color.red": "#d20f39", - "color.green": "#40a02b", - "color.yellow": "#df8e1d", - "color.blue": "#1e66f5", - "color.magenta": "#8839ef", - "color.teal": "#179299", - "color.orange": "#fe640b", - "theme.bg": "transparentize(#eff1f5, 0.3)", - "theme.fg": "#4c4f69", -}; - -export const darkColors = { - "theme.scheme": "dark", - "color.red": "#e78284", - "color.green": "#a6d189", - "color.yellow": "#e5c890", - "color.blue": "#8caaee", - "color.magenta": "#ca9ee6", - "color.teal": "#81c8be", - "color.orange": "#ef9f76", - "theme.bg": "transparentize(#303446, 0.3)", - "theme.fg": "#c6d0f5", -}; - -export const Theme = ({ name, icon = " ", ...options }) => ({ - name, - icon, - options: { - "theme.name": name, - "theme.icon": icon, - ...options, - }, -}); - -let settingsDialog; -export async function openSettings() { - if (settingsDialog) return settingsDialog.present(); - - try { - settingsDialog = (await import("./SettingsDialog.js")).default; - settingsDialog.present(); - } catch (error) { - if (error instanceof Error) console.error(error.message); - } -} diff --git a/modules/home-manager/desktops/hyprland/ags/js/settings/wallpaper.js b/modules/home-manager/desktops/hyprland/ags/js/settings/wallpaper.js deleted file mode 100644 index fe9d5c2..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/settings/wallpaper.js +++ /dev/null @@ -1,19 +0,0 @@ -import options from "../options.js"; -import { exec, execAsync } from "resource:///com/github/Aylur/ags/utils.js"; -import { dependencies } from "../utils.js"; - -export function initWallpaper() { - if (dependencies(["swww"])) { - exec("swww init"); - - options.desktop.wallpaper.img.connect("changed", wallpaper); - } -} - -export function wallpaper() { - if (!dependencies(["swww"])) return; - - execAsync(["swww", "img", options.desktop.wallpaper.img.value]).catch((err) => - console.error(err), - ); -} diff --git a/modules/home-manager/desktops/hyprland/ags/js/themes.js b/modules/home-manager/desktops/hyprland/ags/js/themes.js deleted file mode 100644 index df4148a..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/themes.js +++ /dev/null @@ -1,77 +0,0 @@ -/** - * A Theme is a set of options that will be applied - * ontop of the default values. see options.js for possible options - */ -import { Theme, WP, lightColors, darkColors } from "./settings/theme.js"; - -export default [ - Theme({ - name: "Frappé", - icon: "󰄛", - "desktop.screen_corners": false, - "desktop.clock.enable": false, - "bar.style": "separated", - "bar.separators": false, - "desktop.wallpaper.img": - WP + "wallpapers/Lakeside/lakeside_2019_midnight.png", - ...darkColors, - }), - Theme({ - name: "Latte", - icon: "󰄛", - "desktop.screen_corners": false, - "desktop.clock.enable": false, - "bar.style": "separated", - "bar.separators": false, - "desktop.wallpaper.img": - WP + "wallpapers/Lakeside/Lakeside_2019_Teal_NoDeer_UHD2.png", - ...lightColors, - "theme.widget.bg": "$accent", - "theme.widget.opacity": 64, - }), - /*Theme({ - name: "Leaves", - icon: "󰌪", - "desktop.wallpaper.img": WP + "leaves.jpg", - "theme.accent.accent": "$green", - "theme.accent.gradient": "to right, $accent, darken($accent, 14%)", - "theme.widget.opacity": 92, - "border.opacity": 86, - "theme.bg": "transparentize(#171717, 0.3)", - "bar.style": "floating", - radii: 0, - }), - Theme({ - name: "Ivory", - icon: "󰟆", - ...lightColors, - "desktop.wallpaper.img": WP + "ivory.png", - "desktop.wallpaper.fg": "$bg_color", - "desktop.screen_corners": false, - "bar.style": "separated", - "theme.widget.bg": "$accent", - "theme.widget.opacity": 64, - "desktop.drop_shadow": false, - "border.width": 2, - "border.opacity": 0, - "theme.accent.gradient": "to right, $accent, darken($accent, 6%)", - "hypr.inactive_border": "rgba(111111FF)", - "bar.separators": false, - }), - Theme({ - name: "Space", - icon: "", - "desktop.wallpaper.img": WP + "space.jpg", - spacing: 11, - padding: 10, - radii: 12, - "theme.accent.accent": "$magenta", - "desktop.screen_corners": false, - "desktop.clock.enable": false, - "bar.separators": false, - "bar.icon": "", - "theme.bg": "transparentize(#171717, 0.3)", - "theme.widget.opacity": 95, - "bar.flat_buttons": false, - }),*/ -]; diff --git a/modules/home-manager/desktops/hyprland/ags/js/utils.js b/modules/home-manager/desktops/hyprland/ags/js/utils.js deleted file mode 100644 index 3dcf568..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/utils.js +++ /dev/null @@ -1,99 +0,0 @@ -import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; -import cairo from "cairo"; -import icons from "./icons.js"; -import Gdk from "gi://Gdk"; -import GLib from "gi://GLib"; - -/** - * @param {number} length - * @param {number=} start - * @returns {Array} - */ -export function range(length, start = 1) { - return Array.from({ length }, (_, i) => i + start); -} - -/** - * @param {Array<[string, string] | string[]>} collection - * @param {string} item - * @returns {string} - */ -export function substitute(collection, item) { - return collection.find(([from]) => from === item)?.[1] || item; -} - -/** - * @param {(monitor: number) => any} widget - * @returns {Array} - */ -export function forMonitors(widget) { - const n = Gdk.Display.get_default()?.get_n_monitors() || 1; - return range(n, 0).map(widget).flat(1); -} - -/** - * @param {import('gi://Gtk?version=3.0').default.Widget} widget - * @returns {any} - missing cairo type - */ -export function createSurfaceFromWidget(widget) { - const alloc = widget.get_allocation(); - const surface = new cairo.ImageSurface( - cairo.Format.ARGB32, - alloc.width, - alloc.height, - ); - const cr = new cairo.Context(surface); - cr.setSourceRGBA(255, 255, 255, 0); - cr.rectangle(0, 0, alloc.width, alloc.height); - cr.fill(); - widget.draw(cr); - - return surface; -} - -/** @param {string} icon */ -export function getAudioTypeIcon(icon) { - const substitues = [ - ["audio-headset-bluetooth", icons.audio.type.headset], - ["audio-card-analog-usb", icons.audio.type.speaker], - ["audio-card-analog-pci", icons.audio.type.card], - ]; - - return substitute(substitues, icon); -} - -/** @param {import('types/service/applications').Application} app */ -export function launchApp(app) { - Utils.execAsync(["hyprctl", "dispatch", "exec", `sh -c ${app.executable}`]); - app.frequency += 1; -} - -/** @param {Array} bins */ -export function dependencies(bins) { - const deps = bins.map((bin) => { - const has = Utils.exec(`which ${bin}`); - if (!has) print(`missing dependency: ${bin}`); - - return !!has; - }); - - return deps.every((has) => has); -} - -/** @param {string} img - path to an img file */ -export function blurImg(img) { - const cache = Utils.CACHE_DIR + "/media"; - return new Promise((resolve) => { - if (!img) resolve(""); - - const dir = cache + "/blurred"; - const blurred = dir + img.substring(cache.length); - - if (GLib.file_test(blurred, GLib.FileTest.EXISTS)) return resolve(blurred); - - Utils.ensureDirectory(dir); - Utils.execAsync(["convert", img, "-blur", "0x22", blurred]) - .then(() => resolve(blurred)) - .catch(() => resolve("")); - }); -} diff --git a/modules/home-manager/desktops/hyprland/ags/js/variables.js b/modules/home-manager/desktops/hyprland/ags/js/variables.js deleted file mode 100644 index f3a94c3..0000000 --- a/modules/home-manager/desktops/hyprland/ags/js/variables.js +++ /dev/null @@ -1,92 +0,0 @@ -import Variable from "resource:///com/github/Aylur/ags/variable.js"; -import GLib from "gi://GLib"; -import options from "./options.js"; - -const intval = options.systemFetchInterval; - -export const clock = Variable(GLib.DateTime.new_now_local(), { - poll: [1000, () => GLib.DateTime.new_now_local()], -}); - -export const uptime = Variable("", { - poll: [ - 60_000, - "cat /proc/uptime", - (line) => { - const uptime = Number.parseInt(line.split(".")[0]) / 60; - if (uptime > 18 * 60) return "Go Sleep"; - - const h = Math.floor(uptime / 60); - const s = Math.floor(uptime % 60); - return `${h}:${s < 10 ? "0" + s : s}`; - }, - ], -}); - -export const distro = GLib.get_os_info("ID"); - -export const distroIcon = (() => { - switch (distro) { - case "fedora": - return ""; - case "arch": - return ""; - case "nixos": - return ""; - case "debian": - return ""; - case "opensuse-tumbleweed": - return ""; - case "ubuntu": - return ""; - case "endeavouros": - return ""; - default: - return ""; - } -})(); - -/** @type {function([string, string] | string[]): number} */ -const divide = ([total, free]) => - Number.parseInt(free) / Number.parseInt(total); - -export const cpu = Variable(0, { - poll: [ - intval, - "top -b -n 1", - (out) => - divide([ - "100", - out - .split("\n") - .find((line) => line.includes("Cpu(s)")) - ?.split(/\s+/)[1] - .replace(",", ".") || "0", - ]), - ], -}); - -export const ram = Variable(0, { - poll: [ - intval, - "free", - (out) => - divide( - out - .split("\n") - .find((line) => line.includes("Mem:")) - ?.split(/\s+/) - .splice(1, 2) || ["1", "1"], - ), - ], -}); - -export const temp = Variable(0, { - poll: [ - intval, - "cat " + options.temperature, - (n) => { - return Number.parseInt(n) / 100_000; - }, - ], -}); diff --git a/modules/home-manager/desktops/hyprland/ags/package.json b/modules/home-manager/desktops/hyprland/ags/package.json deleted file mode 100644 index c39777e..0000000 --- a/modules/home-manager/desktops/hyprland/ags/package.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "ags-dotfiles", - "version": "1.7.5", - "description": "My config files for AGS", - "main": "config.js", - "scripts": { - "check": "tsc --noEmit", - "lint": "eslint . --fix", - "stylelint": "stylelint ./scss --fix", - "format": "prettier --write ." - }, - "repository": { - "type": "git", - "url": "git+https://github.com/Aylur/dotfiles.git" - }, - "author": "Aylur", - "bugs": { - "url": "https://github.com/Aylur/dotfiles/issues" - }, - "homepage": "https://github.com/Aylur/dotfiles#readme", - "kofi": "https://ko-fi.com/aylur", - "devDependencies": { - "@girs/dbusmenugtk3-0.4": "^0.4.0-3.2.0", - "@girs/gobject-2.0": "^2.76.1-3.2.3", - "@girs/gtk-3.0": "^3.24.39-3.2.2", - "@girs/gvc-1.0": "^1.0.0-3.1.0", - "@girs/nm-1.0": "^1.43.1-3.1.0", - "@typescript-eslint/eslint-plugin": "^5.33.0", - "@typescript-eslint/parser": "^5.33.0", - "eslint": "^8.44.0", - "prettier": "^3.2.5", - "stylelint-config-standard-scss": "^10.0.0", - "typescript": "^5.3.3" - } -} diff --git a/modules/home-manager/desktops/hyprland/ags/scss/common/a11y-button.scss b/modules/home-manager/desktops/hyprland/ags/scss/common/a11y-button.scss deleted file mode 100644 index 7f3213d..0000000 --- a/modules/home-manager/desktops/hyprland/ags/scss/common/a11y-button.scss +++ /dev/null @@ -1,45 +0,0 @@ -@import "./button"; - -@mixin accs-button($flat: false, $reactive: true) { - @include button($flat: true, $reactive: false, $focusable: false); - color: $fg-color; - - > * { - border-radius: $radii; - transition: $transition; - - @if $flat { - background-color: transparent; - box-shadow: none; - } @else { - background-color: $widget-bg; - box-shadow: inset 0 0 0 $border-width $border-color; - } - } - - @if $reactive { - &:focus > *, - &.focused > * { - @include button-focus; - } - - &:hover > * { - @include button-hover; - } - - &:active, - &.active, - &.on, - &:checked { - > * { - @include button-active; - } - - &:hover > * { - box-shadow: - inset 0 0 0 $border-width $border-color, - inset 0 0 0 99px $hover; - } - } - } -} diff --git a/modules/home-manager/desktops/hyprland/ags/scss/common/button.scss b/modules/home-manager/desktops/hyprland/ags/scss/common/button.scss deleted file mode 100644 index 5521486..0000000 --- a/modules/home-manager/desktops/hyprland/ags/scss/common/button.scss +++ /dev/null @@ -1,69 +0,0 @@ -@mixin button-focus() { - box-shadow: inset 0 0 0 $border-width $accent; - background-color: $hover; - color: $hover-fg; -} - -@mixin button-hover() { - box-shadow: inset 0 0 0 $border-width $border-color; - background-color: $hover; - color: $hover-fg; -} - -@mixin button-active() { - box-shadow: inset 0 0 0 $border-width $border-color; - background-image: $active-gradient; - background-color: $accent; - color: $accent-fg; -} - -@mixin button-disabled() { - box-shadow: none; - background-color: transparent; - color: transparentize($fg-color, 0.7); -} - -@mixin button($flat: false, $reactive: true, $radii: $radii, $focusable: true) { - all: unset; - transition: $transition; - border-radius: $radii; - color: $fg-color; - - @if $flat { - background-color: transparent; - background-image: none; - box-shadow: none; - } @else { - background-color: $widget-bg; - box-shadow: inset 0 0 0 $border-width $border-color; - } - - @if $reactive { - @if $focusable { - &:focus { - @include button-focus; - } - } - - &:hover { - @include button-hover; - } - - &:active, - &.on, - &.active, - &:checked { - @include button-active; - - &:hover { - box-shadow: - inset 0 0 0 $border-width $border-color, - inset 0 0 0 99px $hover; - } - } - } - - &:disabled { - @include button-disabled; - } -} diff --git a/modules/home-manager/desktops/hyprland/ags/scss/common/floating-widget.scss b/modules/home-manager/desktops/hyprland/ags/scss/common/floating-widget.scss deleted file mode 100644 index d1d5ede..0000000 --- a/modules/home-manager/desktops/hyprland/ags/scss/common/floating-widget.scss +++ /dev/null @@ -1,12 +0,0 @@ -@mixin floating-widget { - @if $drop-shadow { - box-shadow: 0 0 5px 0 $shadow; - } - - margin: max($spacing, 8px); - border: $border-width solid $popover-border-color; - background-color: $bg-color; - color: $fg-color; - border-radius: $popover-radius; - padding: $popover-padding; -} diff --git a/modules/home-manager/desktops/hyprland/ags/scss/common/hidden.scss b/modules/home-manager/desktops/hyprland/ags/scss/common/hidden.scss deleted file mode 100644 index fc0f404..0000000 --- a/modules/home-manager/desktops/hyprland/ags/scss/common/hidden.scss +++ /dev/null @@ -1,15 +0,0 @@ -@mixin hidden { - background-color: transparent; - background-image: none; - border-color: transparent; - box-shadow: none; - -gtk-icon-transform: scale(0); - - * { - background-color: transparent; - background-image: none; - border-color: transparent; - box-shadow: none; - -gtk-icon-transform: scale(0); - } -} diff --git a/modules/home-manager/desktops/hyprland/ags/scss/common/menu.scss b/modules/home-manager/desktops/hyprland/ags/scss/common/menu.scss deleted file mode 100644 index 4622c9f..0000000 --- a/modules/home-manager/desktops/hyprland/ags/scss/common/menu.scss +++ /dev/null @@ -1,29 +0,0 @@ -window.popup { - > * { - border: none; - box-shadow: none; - } - - menu { - border-radius: $popover-radius; - background-color: $bg-color; - padding: $popover-padding; - border: $border-width solid $popover-border-color; - - separator { - background-color: $border-color; - } - - menuitem { - @include button; - padding: $spacing/2; - margin: $spacing/2 0; - &:first-child { - margin-top: 0; - } - &:last-child { - margin-bottom: 0; - } - } - } -} diff --git a/modules/home-manager/desktops/hyprland/ags/scss/common/scrollable.scss b/modules/home-manager/desktops/hyprland/ags/scss/common/scrollable.scss deleted file mode 100644 index 35be0c8..0000000 --- a/modules/home-manager/desktops/hyprland/ags/scss/common/scrollable.scss +++ /dev/null @@ -1,28 +0,0 @@ -@mixin scrollable { - scrollbar, - scrollbar * { - all: unset; - } - - scrollbar.vertical { - transition: $transition; - background-color: transparentize($bg-color, 0.7); - - &:hover { - background-color: transparentize($bg-color, 0.3); - - slider { - background-color: transparentize($fg-color, 0.3); - min-width: 0.6em; - } - } - } - - scrollbar.vertical slider { - background-color: transparentize($fg-color, 0.5); - border-radius: $radii; - min-width: 0.4em; - min-height: 2em; - transition: $transition; - } -} diff --git a/modules/home-manager/desktops/hyprland/ags/scss/common/slider.scss b/modules/home-manager/desktops/hyprland/ags/scss/common/slider.scss deleted file mode 100644 index fb2bfa5..0000000 --- a/modules/home-manager/desktops/hyprland/ags/scss/common/slider.scss +++ /dev/null @@ -1,79 +0,0 @@ -@import "./unset"; - -@mixin slider( - $width: 0.7em, - $slider-width: 0.5em, - $gradient: $active-gradient, - $slider: true, - $focusable: true, - $radii: $radii -) { - @include unset($rec: true); - - trough { - transition: $transition; - border-radius: $radii; - border: $border; - background-color: $widget-bg; - min-height: $width; - min-width: $width; - - highlight, - progress { - border-radius: max($radii - $border-width, 0); - background-image: $gradient; - min-height: $width; - min-width: $width; - } - } - - slider { - box-shadow: none; - background-color: transparent; - border: $border-width solid transparent; - transition: $transition; - border-radius: $radii; - min-height: $width; - min-width: $width; - margin: -$slider-width; - } - - &:hover { - trough { - background-color: $hover; - } - - slider { - @if $slider { - background-color: $fg-color; - border-color: $border-color; - - @if $drop-shadow { - box-shadow: 0 0 3px 0 $shadow; - } - } - } - } - - &:disabled { - highlight, - progress { - background-color: transparentize($fg-color, 0.4); - background-image: none; - } - } - - @if $focusable { - trough:focus { - background-color: $hover; - box-shadow: inset 0 0 0 $border-width $accent; - - slider { - @if $slider { - background-color: $fg-color; - box-shadow: inset 0 0 0 $border-width $accent; - } - } - } - } -} diff --git a/modules/home-manager/desktops/hyprland/ags/scss/common/spacing.scss b/modules/home-manager/desktops/hyprland/ags/scss/common/spacing.scss deleted file mode 100644 index ae7737d..0000000 --- a/modules/home-manager/desktops/hyprland/ags/scss/common/spacing.scss +++ /dev/null @@ -1,45 +0,0 @@ -@mixin spacing($multiplier: 1, $spacing: $spacing, $rec: false) { - &.horizontal > * { - margin: 0 $spacing * $multiplier / 2; - &:first-child { - margin-left: 0; - } - &:last-child { - margin-right: 0; - } - } - - &.vertical > * { - margin: $spacing * $multiplier / 2 0; - &:first-child { - margin-top: 0; - } - &:last-child { - margin-bottom: 0; - } - } - - @if $rec { - box { - &.horizontal > * { - margin: 0 $spacing * $multiplier / 2; - &:first-child { - margin-left: 0; - } - &:last-child { - margin-right: 0; - } - } - - &.vertical > * { - margin: $spacing * $multiplier / 2 0; - &:first-child { - margin-top: 0; - } - &:last-child { - margin-bottom: 0; - } - } - } - } -} diff --git a/modules/home-manager/desktops/hyprland/ags/scss/common/switch.scss b/modules/home-manager/desktops/hyprland/ags/scss/common/switch.scss deleted file mode 100644 index 27082e5..0000000 --- a/modules/home-manager/desktops/hyprland/ags/scss/common/switch.scss +++ /dev/null @@ -1,16 +0,0 @@ -@import "./button"; - -@mixin switch { - @include button; - - slider { - background-color: $accent-fg; - border-radius: $radii; - min-width: 24px; - min-height: 24px; - } - - image { - color: transparent; - } -} diff --git a/modules/home-manager/desktops/hyprland/ags/scss/common/text-border.scss b/modules/home-manager/desktops/hyprland/ags/scss/common/text-border.scss deleted file mode 100644 index fbfd156..0000000 --- a/modules/home-manager/desktops/hyprland/ags/scss/common/text-border.scss +++ /dev/null @@ -1,13 +0,0 @@ -@mixin text-border { - text-shadow: - -1 * $border-width -1 * $border-width 0 $border-color, - $border-width $border-width 0 $border-color, - -1 * $border-width $border-width 0 $border-color, - $border-width -1 * $border-width 0 $border-color; - - -gtk-icon-shadow: - -1 * $border-width -1 * $border-width 0 $border-color, - $border-width $border-width 0 $border-color, - -1 * $border-width $border-width 0 $border-color, - $border-width -1 * $border-width 0 $border-color; -} diff --git a/modules/home-manager/desktops/hyprland/ags/scss/common/tooltip.scss b/modules/home-manager/desktops/hyprland/ags/scss/common/tooltip.scss deleted file mode 100644 index b19aff2..0000000 --- a/modules/home-manager/desktops/hyprland/ags/scss/common/tooltip.scss +++ /dev/null @@ -1,18 +0,0 @@ -tooltip { - * { - all: unset; - } - - background-color: transparent; - border: none; - - > * > * { - background-color: $bg-color; - border-radius: $radii; - border: $border-width solid $popover-border-color; - color: $fg-color; - padding: 8px; - margin: 4px; - box-shadow: 0 0 3px 0 $shadow; - } -} diff --git a/modules/home-manager/desktops/hyprland/ags/scss/common/unset.scss b/modules/home-manager/desktops/hyprland/ags/scss/common/unset.scss deleted file mode 100644 index 1dce620..0000000 --- a/modules/home-manager/desktops/hyprland/ags/scss/common/unset.scss +++ /dev/null @@ -1,9 +0,0 @@ -@mixin unset($rec: false) { - all: unset; - - @if $rec { - * { - all: unset; - } - } -} diff --git a/modules/home-manager/desktops/hyprland/ags/scss/common/widget.scss b/modules/home-manager/desktops/hyprland/ags/scss/common/widget.scss deleted file mode 100644 index b3d9694..0000000 --- a/modules/home-manager/desktops/hyprland/ags/scss/common/widget.scss +++ /dev/null @@ -1,7 +0,0 @@ -@mixin widget { - transition: $transition; - border-radius: $radii; - color: $fg-color; - background-color: $widget-bg; - border: $border; -} diff --git a/modules/home-manager/desktops/hyprland/ags/scss/main.scss b/modules/home-manager/desktops/hyprland/ags/scss/main.scss deleted file mode 100644 index 5e491c0..0000000 --- a/modules/home-manager/desktops/hyprland/ags/scss/main.scss +++ /dev/null @@ -1,36 +0,0 @@ -@import "/tmp/ags/scss/options"; -@import "./variables"; - -// common -@import "./common/unset"; -@import "./common/widget"; -@import "./common/button"; -@import "./common/a11y-button"; -@import "./common/floating-widget"; -@import "./common/slider"; -@import "./common/scrollable"; -@import "./common/switch"; -@import "./common/hidden"; -@import "./common/text-border"; -@import "./common/tooltip"; -@import "./common/menu"; -@import "./common/spacing"; - -// widgets -@import "./widgets/about"; -@import "./widgets/applauncher"; -@import "./widgets/bar"; -@import "./widgets/desktop"; -@import "./widgets/notifications"; -@import "./widgets/overview"; -@import "./widgets/osd"; -@import "./widgets/dashboard"; -@import "./widgets/dock"; -@import "./widgets/powermenu"; -@import "./widgets/lockscreen"; -@import "./widgets/media"; -@import "./widgets/quicksettings"; -@import "./widgets/settings"; - -// additional overrides -@import "/tmp/ags/scss/additional"; diff --git a/modules/home-manager/desktops/hyprland/ags/scss/variables.scss b/modules/home-manager/desktops/hyprland/ags/scss/variables.scss deleted file mode 100644 index 578729b..0000000 --- a/modules/home-manager/desktops/hyprland/ags/scss/variables.scss +++ /dev/null @@ -1,32 +0,0 @@ -// variables are defined in options.js -// these ones are derived from those - -$hover: transparentize($_widget-bg, ($widget-opacity * 0.9) / 100); -$widget-bg: transparentize($_widget-bg, $widget-opacity / 100); -$active-gradient: linear-gradient($accent-gradient); - -$hover-fg: if( - $color-scheme == "dark", - lighten($fg-color, 10%), - darken($fg-color, 8%) -); - -$border-color: transparentize($_border-color, $border-opacity / 100); -$border: $border-width solid $border-color; - -$text-shadow: 2px 2px 2px $shadow; - -$popover-border-color: transparentize( - $_border-color, - max(($border-opacity - 1) / 100, 0) -); -$popover-padding: $padding * $popover-padding-multiplier; -$popover-radius: if($radii == 0, 0, $radii + $popover-padding); - -$wm-gaps: floor($spacing * $wm-gaps-multiplier); - -$shader-fg: #fff; - -* { - font-size: $font-size; -} diff --git a/modules/home-manager/desktops/hyprland/ags/scss/widgets/about.scss b/modules/home-manager/desktops/hyprland/ags/scss/widgets/about.scss deleted file mode 100644 index ae72356..0000000 --- a/modules/home-manager/desktops/hyprland/ags/scss/widgets/about.scss +++ /dev/null @@ -1,62 +0,0 @@ -window#about { - @include unset; - - .window-content { - @include floating-widget; - min-width: 300px; - } - - .avatar { - min-width: 200px; - min-height: 200px; - background-size: cover; - border: $border; - margin: $spacing 0; - } - - .labels { - .title { - font-size: 1.2em; - } - - .author { - color: transparentize($fg-color, 0.2); - } - - .version { - margin-top: $spacing; - margin-bottom: $spacing * 2; - border-radius: $radii; - background-color: $widget-bg; - color: $accent; - padding: $padding; - } - } - - .buttons { - padding-bottom: $popover-padding; - - button { - @include button; - padding: $padding; - - &:first-child { - border-radius: $radii $radii 0 0; - } - - &:last-child { - border-radius: 0 0 $radii $radii; - } - } - } - - .dont-show { - @include button; - padding: $padding; - - image { - font-size: 1.4em; - color: $red; - } - } -} diff --git a/modules/home-manager/desktops/hyprland/ags/scss/widgets/applauncher.scss b/modules/home-manager/desktops/hyprland/ags/scss/widgets/applauncher.scss deleted file mode 100644 index 913adb7..0000000 --- a/modules/home-manager/desktops/hyprland/ags/scss/widgets/applauncher.scss +++ /dev/null @@ -1,71 +0,0 @@ -window#applauncher .window-content { - @include floating_widget; - - entry { - @include button; - padding: $padding; - margin-bottom: $spacing; - - label, - image { - color: $fg-color; - } - } - - separator { - min-height: 1px; - background-color: $hover; - } - - scrolledwindow { - @include scrollable; - min-width: $applauncher-width; - min-height: $applauncher-height; - } - - button.app-item { - @include button($flat: true, $reactive: false); - > box { - @include spacing(0.5); - } - transition: $transition; - padding: $padding; - - label { - transition: $transition; - - &.title { - color: $fg-color; - } - - &.description { - color: transparentize($fg-color, 0.3); - } - } - - image { - transition: $transition; - } - - &:hover, - &:focus { - .title { - color: $accent; - } - - image { - -gtk-icon-shadow: 2px 2px $accent; - } - } - - &:active { - background-color: transparentize($accent, 0.5); - border-radius: $radii; - box-shadow: inset 0 0 0 $border-width $border-color; - - .title { - color: $fg-color; - } - } - } -} diff --git a/modules/home-manager/desktops/hyprland/ags/scss/widgets/bar.scss b/modules/home-manager/desktops/hyprland/ags/scss/widgets/bar.scss deleted file mode 100644 index 9506a90..0000000 --- a/modules/home-manager/desktops/hyprland/ags/scss/widgets/bar.scss +++ /dev/null @@ -1,283 +0,0 @@ -$bar-spacing: $spacing / 2; -$button-radius: if( - $bar-style == "floating", - max(0, $radii - $bar-spacing), - $radii -); - -@mixin panel-button($flat: $bar-flat-buttons, $reactive: true) { - @include unset; - - @if $bar-style == "separated" { - transition: $transition; - - > * { - @include floating-widget; - border-radius: $radii; - margin: $wm-gaps $bar-spacing; - transition: $transition; - } - - &:hover > * { - color: $hover-fg; - - @if $drop-shadow { - box-shadow: - 0 0 min(6px, $spacing/2) 0 $shadow, - inset 0 0 0 99px $hover; - } @else { - box-shadow: inset 0 0 0 99px $hover; - } - } - - &:active > *, - &.active > * { - label, - image { - color: $accent-fg; - } - background-image: $active-gradient; - background-color: $accent; - } - } @else { - @include accs-button($flat, $reactive); - - > * { - border-radius: $button-radius; - margin: $bar-spacing; - } - } - - label, - image { - font-weight: bold; - } - - > * { - padding: $padding * 0.4 $padding * 0.8; - } -} - -.panel { - @if $bar-style == "normal" { - background-color: $bg-color; - } - - @if not $screen-corners and $bar-style == "normal" { - @if $bar-position == "bottom" { - border-top: $border; - } @else { - border-bottom: $border; - } - } - - @if $bar-style == "floating" { - @include floating-widget; - border-radius: $radii; - margin: $wm-gaps; - padding: 0; - } - - @if $bar-style == "separated" { - > .end > button:last-child > * { - margin-right: $wm-gaps; - } - - > .start > button:first-child > * { - margin-left: $wm-gaps; - } - } - - .panel-button { - @include panel-button; - } - - .tray-item, - .color-picker { - @include panel-button($flat: true); - } - - separator { - background-color: transparentize($fg-color, 0.86); - border-radius: $radii; - min-height: 5px; - min-width: 5px; - } - - .overview { - label { - color: transparentize($accent, 0.2); - } - &:hover label { - color: $accent; - } - &:active label, - &.active label { - color: $accent-fg; - } - } - - .powermenu, - .recorder { - image { - color: transparentize($red, 0.3); - } - &:hover image { - color: transparentize($red, 0.15); - } - &:active image { - color: $red; - } - } - - .focused-client > box > box, - .quicksettings > box > box { - @include spacing( - $spacing: if($bar-spacing == 0, $padding / 2, $bar-spacing) - ); - } - - /* stylelint-disable-next-line selector-not-notation */ - .quicksettings:not(.active):not(:active) { - .bluetooth { - color: $blue; - } - - .battery { - &.low { - color: $red; - } - &.charged, - &.charging { - color: $green; - } - } - } - - .media { - &.spotify image { - color: $green; - } - &.firefox image { - color: $orange; - } - &.mpv image { - color: $magenta; - } - } - - .notifications { - image { - color: $yellow; - } - } - - .battery-bar { - .font-icon { - font-size: 1.15em; - } - - @if $battery-bar-full { - > box { - padding: 0; - } - } - - image, - .font-icon { - margin-right: $bar-spacing * 0.5; - } - - levelbar trough { - @include widget; - min-width: $battery-bar-width; - min-height: $battery-bar-height; - - block.filled { - border-radius: max($radii - $border-width, 0); - background-image: $active-gradient; - } - } - - @mixin color($color) { - image, - label { - color: $color; - } - - block.filled { - background-image: linear-gradient( - to right, - $color, - lighten($color, 6%) - ); - } - } - - .medium { - @include color($yellow); - } - .low { - @include color($red); - } - .charging { - @include color($green); - } - &:active { - @include color($accent-fg); - } - - .whole-button { - label { - color: $fg-color; - text-shadow: $text-shadow; - } - - trough, - block.filled { - border-radius: $button-radius; - } - - @if $bar-style == "separated" { - trough { - border: none; - } - } - } - } - - .workspaces button { - all: unset; - - .indicator { - font-size: 0; - min-width: 8px; - min-height: 8px; - border-radius: $radii * 0.6; - box-shadow: inset 0 0 0 $border-width $border-color; - margin: 0 $padding/2; - transition: $transition/2; - background-color: transparentize($fg-color, 0.8); - } - - &.occupied .indicator { - background-color: transparentize($fg-color, 0.2); - min-width: 10px; - min-height: 10px; - } - - &:hover .indicator { - box-shadow: inset 0 0 0 10px transparentize($fg-color, 0.8); - } - - &.active .indicator, - &:active .indicator { - background-color: $accent; - } - - &.active .indicator { - min-width: 24px; - min-height: 16px; - } - } -} diff --git a/modules/home-manager/desktops/hyprland/ags/scss/widgets/dashboard.scss b/modules/home-manager/desktops/hyprland/ags/scss/widgets/dashboard.scss deleted file mode 100644 index f979626..0000000 --- a/modules/home-manager/desktops/hyprland/ags/scss/widgets/dashboard.scss +++ /dev/null @@ -1,131 +0,0 @@ -@mixin calendar { - @include widget; - padding: $padding * 2 $padding * 2 0; - - calendar { - all: unset; - - &.button { - @include button($flat: true); - } - - &:selected { - box-shadow: - inset 0 -8px 0 0 transparentize($accent, 0.5), - inset 0 0 0 1px $accent; - border-radius: $radii * 0.6; - } - - &.header { - background-color: transparent; - border: none; - color: transparentize($fg-color, 0.5); - } - - &.highlight { - background-color: transparent; - color: transparentize($accent, 0.5); - } - - &:indeterminate { - color: transparentize($fg-color, 0.9); - } - font-size: 1.1em; - padding: 0.2em; - } -} - -window#dashboard .window-content { - @include floating-widget; - - .notifications { - min-width: $notifications-width; - - .header { - margin-bottom: $spacing; - margin-right: $spacing; - - > label { - margin-left: $radii / 2; - } - - button { - @include button; - padding: $padding/2 $padding; - } - } - - .notification-scrollable { - @include scrollable; - } - - .notification-list { - margin-right: $spacing; - } - - .notification { - @include notification; - - > box { - @include widget; - padding: $padding; - margin-bottom: $spacing; - } - } - - .placeholder { - image { - font-size: 7em; - } - label { - font-size: 1.2em; - } - } - } - - separator { - background-color: $popover-border-color; - min-width: 2px; - border-radius: $radii; - margin-right: $spacing; - } - - .datemenu, - .system-info { - @include spacing; - } - - .clock-box { - padding: $padding; - - .clock { - font-size: 5em; - } - - .uptime { - color: transparentize($fg-color, 0.2); - } - } - - .calendar { - @include calendar; - } - - .circular-progress-box { - @include widget; - padding: $padding; - - .circular-progress { - min-height: $sys-info-size; - min-width: $sys-info-size; - margin: $padding/2; - font-size: $padding; - background-color: $bg-color; - color: $accent; - - image { - font-size: 1.8em; - } - } - } -} diff --git a/modules/home-manager/desktops/hyprland/ags/scss/widgets/desktop.scss b/modules/home-manager/desktops/hyprland/ags/scss/widgets/desktop.scss deleted file mode 100644 index 4f3197c..0000000 --- a/modules/home-manager/desktops/hyprland/ags/scss/widgets/desktop.scss +++ /dev/null @@ -1,81 +0,0 @@ -window.corner .corner { - background-color: $bg-color; - border-radius: $radii * 2; - min-width: 2px; - min-height: 2px; -} - -window.desktop { - @if $bar-style == "normal" { - border-radius: if($screen-corners, $radii * 2, 0); - box-shadow: inset 0 0 $wm-gaps / 2 0 $shadow; - } - - .clock-box-shadow { - border: 5px solid $wallpaper-fg; - border-radius: $radii; - - .clock-box { - border-radius: max($radii - 5px, 0); - padding: 0 14px; - - .clock { - color: $wallpaper-fg; - font-size: 140px; - font-family: $mono-font; - } - - .separator-box { - padding: 24px 12px; - - separator { - border-radius: $radii; - min-width: 16px; - min-height: 16px; - background-color: $wallpaper-fg; - } - } - } - } - - .date { - color: $wallpaper-fg; - font-size: 48px; - } - - @if $drop-shadow { - .clock-box-shadow, - separator { - box-shadow: 2px 2px 2px 0 $shadow; - } - - .clock-box { - box-shadow: inset 2px 2px 2px 0 $shadow; - } - - label { - text-shadow: $text-shadow; - } - } @else { - .clock-box-shadow { - box-shadow: - 0 0 0 $border-width $border-color, - inset 0 0 0 $border-width $border-color; - } - - separator { - border: $border; - } - - label { - @include text-border; - } - } -} - -.desktop-menu { - image { - margin-left: -14px; - margin-right: 6px; - } -} diff --git a/modules/home-manager/desktops/hyprland/ags/scss/widgets/dock.scss b/modules/home-manager/desktops/hyprland/ags/scss/widgets/dock.scss deleted file mode 100644 index 98e2778..0000000 --- a/modules/home-manager/desktops/hyprland/ags/scss/widgets/dock.scss +++ /dev/null @@ -1,44 +0,0 @@ -@mixin dock($spacing: $spacing * 0.7) { - separator { - border-radius: $radii; - background-color: transparentize($fg-color, 0.8); - margin: 0 $spacing; - min-width: 2px; - min-height: 2em; - } - - button { - @include accs-button($flat: true); - - .box { - margin: $spacing / 2; - } - - image { - margin: $padding; - - @if $color-scheme == "light" { - -gtk-icon-shadow: $text-shadow; - } - } - - .indicator { - min-width: 8px; - min-height: 8px; - background-color: $fg-color; - border-radius: $radii; - margin-bottom: $padding/2; - - &.focused { - background-image: $active-gradient; - } - } - } -} - -window.floating-dock .dock { - @include dock; - @include floating-widget; - border-radius: if($radii == 0, 0, $radii + $spacing / 2); - padding: $spacing / 2; -} diff --git a/modules/home-manager/desktops/hyprland/ags/scss/widgets/lockscreen.scss b/modules/home-manager/desktops/hyprland/ags/scss/widgets/lockscreen.scss deleted file mode 100644 index 010a69f..0000000 --- a/modules/home-manager/desktops/hyprland/ags/scss/widgets/lockscreen.scss +++ /dev/null @@ -1,26 +0,0 @@ -window.lockscreen { - background-color: rgba(0, 0, 0, 0.25); - - .avatar { - @include widget; - border-radius: $radii * 2; - min-height: 200px; - min-width: 200px; - } - - .content { - @include floating-widget; - padding: $spacing * 4; - } - - spinner { - margin-top: $spacing * 2; - } - - entry { - @include button; - margin-top: $spacing * 2; - padding: $spacing; - min-height: 20px; - } -} diff --git a/modules/home-manager/desktops/hyprland/ags/scss/widgets/media.scss b/modules/home-manager/desktops/hyprland/ags/scss/widgets/media.scss deleted file mode 100644 index a773f9b..0000000 --- a/modules/home-manager/desktops/hyprland/ags/scss/widgets/media.scss +++ /dev/null @@ -1,120 +0,0 @@ -@mixin player-color($color) { - button { - .shuffle.enabled { - color: $color; - } - - .loop { - &.playlist, - &.track { - color: $color; - } - } - - &:active label { - color: $color; - } - } - - .position-slider:hover trough { - background-color: transparentize($color, 0.5); - } - - .player-icon { - color: $color; - } -} - -@mixin media() { - @include widget; - - label { - color: $shader-fg; - text-shadow: $text-shadow; - } - - .blurred-cover, - .cover { - background-size: cover; - background-position: center; - border-radius: $radii * 0.8; - opacity: 0.8; - } - - .cover { - min-height: 100px; - min-width: 100px; - box-shadow: 2px 2px 2px 0 $shadow; - margin: $padding; - opacity: 0.9; - } - - .labels { - margin-top: $padding; - - label { - font-size: 1.1em; - text-shadow: $text-shadow; - - &.title { - font-weight: bold; - } - } - } - - .position-slider { - @include slider( - $width: 0.4em, - $slider: false, - $gradient: linear-gradient($shader-fg, $shader-fg), - $radii: 0 - ); - margin-bottom: $padding/2; - - trough { - border: none; - background-color: transparentize($shader-fg, 0.7); - } - } - - .footer-box { - margin: -$padding/2 $padding $padding/2; - - image { - -gtk-icon-shadow: $text-shadow; - } - } - - .controls button { - @include unset; - - label { - font-size: 2em; - color: transparentize($shader-fg, 0.2); - transition: $transition; - - &.shuffle, - &.loop { - font-size: 1.4em; - } - } - - &:hover label { - color: transparentize($shader-fg, 0.1); - } - - &:active label { - color: $shader-fg; - } - } - - &.spotify { - @include player-color($green); - } - &.firefox { - @include player-color($orange); - } - &.mpv { - @include player-color($magenta); - } -} diff --git a/modules/home-manager/desktops/hyprland/ags/scss/widgets/notifications.scss b/modules/home-manager/desktops/hyprland/ags/scss/widgets/notifications.scss deleted file mode 100644 index 7372cf2..0000000 --- a/modules/home-manager/desktops/hyprland/ags/scss/widgets/notifications.scss +++ /dev/null @@ -1,81 +0,0 @@ -@mixin notification() { - &.critical > box { - box-shadow: inset 0 0 0.5em 0 $red; - } - - &:hover button.close-button { - @include button-hover; - background-color: transparentize($red, 0.5); - } - - .content { - .title { - margin-right: $spacing; - color: $fg-color; - font-size: 1.1em; - } - - .time { - color: transparentize($fg-color, 0.2); - } - - .description { - font-size: 0.9em; - color: transparentize($fg-color, 0.2); - } - - .icon { - border-radius: $radii * 0.8; - margin-right: $spacing; - - &.img { - border: $border; - } - } - } - - box.actions { - @include spacing(0.5); - margin-top: $spacing; - - button { - @include button; - border-radius: $radii * 0.8; - font-size: 1.2em; - padding: $padding * 0.7; - } - } - - button.close-button { - @include button($flat: true); - margin-left: $spacing / 2; - border-radius: $radii * 0.8; - min-width: 1.2em; - min-height: 1.2em; - - &:hover { - background-color: transparentize($red, 0.2); - } - - &:active { - background-image: linear-gradient($red, $red); - } - } -} - -window.notifications { - @include unset; - - .notification { - @include notification; - - > box { - @include floating-widget; - border-radius: $radii; - } - - .description { - min-width: 350px; - } - } -} diff --git a/modules/home-manager/desktops/hyprland/ags/scss/widgets/osd.scss b/modules/home-manager/desktops/hyprland/ags/scss/widgets/osd.scss deleted file mode 100644 index ba815bf..0000000 --- a/modules/home-manager/desktops/hyprland/ags/scss/widgets/osd.scss +++ /dev/null @@ -1,19 +0,0 @@ -window.indicator .progress { - @include floating-widget; - padding: $padding / 2; - border-radius: if($radii == 0, 0, $radii + $padding / 2); - - .fill { - border-radius: $radii; - background-color: $accent; - color: $accent-fg; - - image { - -gtk-icon-transform: scale(0.7); - } - - .font-icon { - font-size: 34px; - } - } -} diff --git a/modules/home-manager/desktops/hyprland/ags/scss/widgets/overview.scss b/modules/home-manager/desktops/hyprland/ags/scss/widgets/overview.scss deleted file mode 100644 index 44569a5..0000000 --- a/modules/home-manager/desktops/hyprland/ags/scss/widgets/overview.scss +++ /dev/null @@ -1,30 +0,0 @@ -window#overview .window-content { - @include floating-widget; - @include spacing; - - .workspace { - &.active > widget { - border-color: $accent; - } - - > widget { - @include widget; - border-radius: if($radii == 0, 0, $radii + $padding); - - &:drop(active) { - border-color: $accent; - } - } - } - - .client { - @include button; - border-radius: $radii; - margin: $padding; - - &.hidden { - @include hidden; - transition: 0; - } - } -} diff --git a/modules/home-manager/desktops/hyprland/ags/scss/widgets/powermenu.scss b/modules/home-manager/desktops/hyprland/ags/scss/widgets/powermenu.scss deleted file mode 100644 index 629a5fd..0000000 --- a/modules/home-manager/desktops/hyprland/ags/scss/widgets/powermenu.scss +++ /dev/null @@ -1,84 +0,0 @@ -window#powermenu, -window#verification { - .shader { - background-color: rgba(0, 0, 0, 0.05); - } -} - -window#verification .window-content { - @include floating-widget; - min-width: 300px; - min-height: 100px; - - .text-box { - .title { - font-size: 1.6em; - } - - .desc { - color: transparentize($fg-color, 0.1); - font-size: 1.1em; - } - } - - .buttons { - @include spacing; - margin-top: $padding; - - button { - @include button; - font-size: 1.5em; - padding: $padding; - } - } -} - -window#powermenu .window-content { - @include floating-widget; - @include spacing(2); - padding: $popover-padding + $spacing * 1.5; - border-radius: if( - $radii == 0, - 0, - $popover-radius + ($popover-padding + $spacing * 1.5) - ); - - button { - @include unset; - - image { - @include button; - border-radius: $popover-radius; - min-width: 1.7em; - min-height: 1.7em; - font-size: 4em; - } - - label, - image { - color: transparentize($fg-color, 0.1); - } - - &:hover { - image { - @include button-hover; - } - label { - color: $fg-color; - } - } - &:focus image { - @include button-focus; - } - &:active image { - @include button-active; - } - - &:focus, - &:active { - label { - color: $accent; - } - } - } -} diff --git a/modules/home-manager/desktops/hyprland/ags/scss/widgets/quicksettings.scss b/modules/home-manager/desktops/hyprland/ags/scss/widgets/quicksettings.scss deleted file mode 100644 index f7965f2..0000000 --- a/modules/home-manager/desktops/hyprland/ags/scss/widgets/quicksettings.scss +++ /dev/null @@ -1,165 +0,0 @@ -window#quicksettings .window-content { - @include floating-widget; - @include spacing; - - .avatar { - @include widget; - opacity: 0.9; - } - - .header { - @include spacing($rec: true); - - button, - .uptime, - .battery { - @include button; - padding: $padding; - font-weight: bold; - min-height: 20px; - min-width: 20px; - - image { - font-size: 1.2em; - } - } - - .battery { - @include spacing($multiplier: 0.5); - } - } - - .battery-progress { - label { - color: $accent-fg; - font-weight: bold; - } - - &.charging label { - font-size: $padding * 2; - } - - &.half label { - color: $fg-color; - } - - progressbar { - @include slider($width: $padding * 3.6); - } - - &.low progressbar { - @include slider( - $width: $padding * 3.6, - $gradient: linear-gradient(to right, $red, $red) - ); - } - } - - .sliders-box { - @include widget; - @include spacing($rec: true); - @include spacing(0); - padding: $padding; - - button { - @include button($flat: true); - padding: $padding / 2; - } - - scale { - @include slider; - margin-left: $spacing * -0.5; - } - - .menu { - margin: $spacing 0; - background-color: $bg-color; - border: $border-width solid $popover-border-color; - border-radius: $radii; - } - } - - .mixer-item { - scale { - @include slider($width: 7px); - } - image { - font-size: 1.2em; - } - } - - .row { - @include spacing($rec: true); - } - - .menu { - @include unset; - @include widget; - @include spacing($rec: true); - padding: $padding; - margin-top: $spacing; - - .title { - @include spacing(0.5); - } - - separator { - margin: 0 $radii / 2; - } - - button { - @include button($flat: true); - padding: $padding / 2; - } - - switch { - @include switch; - } - } - - .toggle-button { - @include button; - font-weight: bold; - - .label-box { - @include spacing(0.5); - } - - button { - @include button($flat: true); - padding: $padding; - - &:first-child { - border-top-right-radius: 0; - border-bottom-right-radius: 0; - } - - &:last-child { - border-top-left-radius: 0; - border-bottom-left-radius: 0; - } - } - - &.active { - background-color: $accent; - - label, - image { - color: $accent-fg; - } - } - } - - .simple-toggle { - @include button; - padding: $padding $padding * 1.1; - } - - .media { - @include spacing; - - .player { - @include media; - } - } -} diff --git a/modules/home-manager/desktops/hyprland/ags/scss/widgets/settings.scss b/modules/home-manager/desktops/hyprland/ags/scss/widgets/settings.scss deleted file mode 100644 index 3e8236b..0000000 --- a/modules/home-manager/desktops/hyprland/ags/scss/widgets/settings.scss +++ /dev/null @@ -1,143 +0,0 @@ -window#settings-dialog { - background-color: $bg-color; - - .page { - @include scrollable; - } - - .page-content { - margin: $spacing; - } - - .sidebar-box { - @include spacing($rec: true); - background-color: $widget-bg; - border-right: $border; - padding: $spacing / 2; - - button { - @include button($flat: true); - padding: $padding / 2 $padding * 2; - } - - scrolledwindow { - @include scrollable; - } - } - - .sidebar-header { - background-color: $widget-bg; - border-right: $border; - border-bottom: $border; - padding: $spacing / 2; - - button { - @include button($flat: true); - padding: $padding / 2 $padding; - } - - button:last-child { - margin-left: $spacing / 2; - } - } - - .sidebar-footer { - background-color: $widget-bg; - border-right: $border; - border-top: $border; - padding: $spacing / 2; - - button { - @include button($flat: true); - padding: $padding / 2 $padding; - } - } - - entry.search { - @include button; - border-radius: 0; - padding: $padding; - } - - .row { - @include widget; - border-radius: 0; - border-bottom-width: 0; - padding: $padding; - transition: border-radius 0; - - &:last-child { - border-radius: 0 0 $radii $radii; - border-bottom-width: $border-width; - } - - &:first-child { - border-radius: $radii $radii 0 0; - } - - &:first-child:last-child { - border-radius: $radii; - } - - .overlay-padding { - min-height: $font-size * 3; - } - - &:hover { - background-color: $hover; - } - - entry, - button { - @include button; - padding: $padding; - } - - switch { - @include switch; - } - - spinbutton { - @include unset; - - entry { - border-radius: $radii 0 0 $radii; - } - - button { - border-radius: 0; - } - - button:last-child { - border-radius: 0 $radii $radii 0; - } - } - - .enum-setter { - label { - background-color: $widget-bg; - border-top: $border; - border-bottom: $border; - padding: 0 $padding; - } - - button:first-child { - border-radius: $radii 0 0 $radii; - } - - button:last-child { - border-radius: 0 $radii $radii 0; - } - } - } - - .id, - .note { - font-size: 0.8em; - color: transparentize($fg-color, $amount: 0.5); - } - - .id { - font-family: $mono-font; - } -} diff --git a/modules/home-manager/desktops/hyprland/ags/tsconfig.json b/modules/home-manager/desktops/hyprland/ags/tsconfig.json deleted file mode 100644 index 0cb538f..0000000 --- a/modules/home-manager/desktops/hyprland/ags/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "ES2022", - "lib": ["ES2022"], - "allowJs": true, - "checkJs": true, - "strict": true, - "noImplicitAny": false, - "baseUrl": ".", - "typeRoots": ["./types"], - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true - } -} diff --git a/modules/home-manager/desktops/hyprland/default.nix b/modules/home-manager/desktops/hyprland/default.nix index 3f3faa3..af58f40 100644 --- a/modules/home-manager/desktops/hyprland/default.nix +++ b/modules/home-manager/desktops/hyprland/default.nix @@ -119,6 +119,8 @@ }; }; + shell.asztal.enable = true; + services.kdeconnect = { enable = true; indicator = true; @@ -164,43 +166,6 @@ ''; }; }; - programs.ags = { - enable = true; - configDir = ./ags; - }; - xdg.configFile.ags.onChange = '' - ${pkgs.procps}/bin/pkill -u $USER -USR2 ags || true - ''; - systemd.user.services.ags = { - Unit = { - Description = "ags"; - PartOf = [ - "graphical-session.target" - "tray.target" - ]; - }; - Service = { - ExecStart = "${pkgs.ags}/bin/ags"; - ExecReload = "${pkgs.coreutils}/bin/kill -SIGUSR2 $MAINPID"; - Restart = "always"; - KillMode = "mixed"; - Environment = "PATH=/run/current-system/sw/bin/:${ - with pkgs; - lib.makeBinPath [ - swww - sassc - glib - brightnessctl - ydotool - kitty - hyprpicker - ] - }"; - }; - Install = { - WantedBy = [ "graphical-session.target" ]; - }; - }; programs.kitty = import ./kitty.nix { inherit pkgs; }; programs.anyrun = import ./anyrun.nix { inherit pkgs; }; services.udiskie.enable = true; @@ -216,7 +181,6 @@ slurp wl-clipboard polkit_gnome - xdg-desktop-portal-gtk # qt/kde packages qt6.qtwayland qt5.qtwayland @@ -243,7 +207,6 @@ xorg.xrandr ]; - dconf.settings."org/gnome/desktop/interface".color-scheme = "prefer-dark"; gtk = { enable = true; theme = { diff --git a/modules/home-manager/packages/default.nix b/modules/home-manager/packages/default.nix index 6d7315e..97acecb 100644 --- a/modules/home-manager/packages/default.nix +++ b/modules/home-manager/packages/default.nix @@ -45,6 +45,7 @@ insomnia avalonia-ilspy ghidra + ida-free # utils libqalculate diff --git a/modules/home-manager/shell/asztal.nix b/modules/home-manager/shell/asztal.nix new file mode 100644 index 0000000..33cba05 --- /dev/null +++ b/modules/home-manager/shell/asztal.nix @@ -0,0 +1,39 @@ +{ + config, + lib, + pkgs, + ... +}: + +with lib; + +let + cfg = config.shell.asztal; +in +{ + options.shell.asztal = { + enable = mkEnableOption (mdDoc "Enable a shell based on AGS"); + }; + + config = mkIf cfg.enable { + systemd.user.services.asztal = { + Unit = { + Description = "asztal"; + PartOf = [ + "graphical-session.target" + "tray.target" + ]; + }; + Service = { + ExecStart = "${pkgs.asztal}/bin/asztal"; + ExecReload = "${pkgs.coreutils}/bin/kill -SIGUSR2 $MAINPID"; + Restart = "always"; + KillMode = "mixed"; + Environment = "PATH=/run/current-system/sw/bin/:${with pkgs; lib.makeBinPath [ ]}"; + }; + Install = { + WantedBy = [ "graphical-session.target" ]; + }; + }; + }; +} diff --git a/modules/nixos/desktops/hyprland.nix b/modules/nixos/desktops/hyprland.nix index 8d5afe5..0c8820e 100644 --- a/modules/nixos/desktops/hyprland.nix +++ b/modules/nixos/desktops/hyprland.nix @@ -33,12 +33,10 @@ in command = "${pkgs.hyprland}/bin/Hyprland &> /dev/null"; user = username; }; - /* - default_session = { - command = "${pkgs.greetd.tuigreet}/bin/tuigreet --asterisks --sessions ${config.services.xserver.displayManager.sessionData.desktops}"; - user = username; - }; - */ + default_session = { + command = "${pkgs.greetd.tuigreet}/bin/tuigreet --asterisks"; + user = username; + }; }; }; diff --git a/modules/nixos/hardware/amdgpu.nix b/modules/nixos/hardware/amdgpu.nix index 55e544d..327d228 100644 --- a/modules/nixos/hardware/amdgpu.nix +++ b/modules/nixos/hardware/amdgpu.nix @@ -27,6 +27,7 @@ in kernelParams = [ "amdgpu.seamless=1" "amdgpu.freesync_video=1" + "initcall_blacklist=simpledrm_platform_driver_init" ]; initrd.kernelModules = [ "amdgpu" ]; }; diff --git a/overlays/asztal/.eslintrc.yml b/overlays/asztal/.eslintrc.yml new file mode 100644 index 0000000..ff96a83 --- /dev/null +++ b/overlays/asztal/.eslintrc.yml @@ -0,0 +1,130 @@ +env: + es2022: true +extends: + - "eslint:recommended" + - "plugin:@typescript-eslint/recommended" +parser: "@typescript-eslint/parser" +parserOptions: + ecmaVersion: 2022 + sourceType: "module" + project: "./tsconfig.json" + warnOnUnsupportedTypeScriptVersion: false +root: true +ignorePatterns: + - types/ +plugins: + - "@typescript-eslint" +rules: + "@typescript-eslint/ban-ts-comment": + - "off" + "@typescript-eslint/no-non-null-assertion": + - "off" + # "@typescript-eslint/no-explicit-any": + # - "off" + "@typescript-eslint/no-unused-vars": + - error + - varsIgnorePattern: (^unused|_$) + argsIgnorePattern: ^(unused|_) + "@typescript-eslint/no-empty-interface": + - "off" + + arrow-parens: + - error + - as-needed + comma-dangle: + - error + - always-multiline + comma-spacing: + - error + - before: false + after: true + comma-style: + - error + - last + curly: + - error + - multi-or-nest + - consistent + dot-location: + - error + - property + eol-last: + - error + eqeqeq: + - error + - always + indent: + - error + - 4 + - SwitchCase: 1 + keyword-spacing: + - error + - before: true + lines-between-class-members: + - error + - always + - exceptAfterSingleLine: true + padded-blocks: + - error + - never + - allowSingleLineBlocks: false + prefer-const: + - error + quotes: + - error + - double + - avoidEscape: true + semi: + - error + - never + nonblock-statement-body-position: + - error + - below + no-trailing-spaces: + - error + no-useless-escape: + - off + max-len: + - error + - code: 100 + func-call-spacing: + - error + array-bracket-spacing: + - error + space-before-function-paren: + - error + - anonymous: never + named: never + asyncArrow: ignore + space-before-blocks: + - error + key-spacing: + - error + object-curly-spacing: + - error + - always +globals: + Widget: readonly + Utils: readonly + App: readonly + Variable: readonly + Service: readonly + pkg: readonly + ARGV: readonly + Debugger: readonly + GIRepositoryGType: readonly + globalThis: readonly + imports: readonly + Intl: readonly + log: readonly + logError: readonly + print: readonly + printerr: readonly + window: readonly + TextEncoder: readonly + TextDecoder: readonly + console: readonly + setTimeout: readonly + setInterval: readonly + clearTimeout: readonly + clearInterval: readonly diff --git a/modules/home-manager/desktops/hyprland/ags/.gitignore b/overlays/asztal/.gitignore similarity index 55% rename from modules/home-manager/desktops/hyprland/ags/.gitignore rename to overlays/asztal/.gitignore index fd61781..f56dbd1 100644 --- a/modules/home-manager/desktops/hyprland/ags/.gitignore +++ b/overlays/asztal/.gitignore @@ -1,5 +1,6 @@ node_modules types package-lock.json -weather_key -setup.sh +bun.lockb +flake.lock +.weather diff --git a/overlays/asztal/assets/battery-flash-symbolic.svg b/overlays/asztal/assets/battery-flash-symbolic.svg new file mode 100644 index 0000000..21b5e33 --- /dev/null +++ b/overlays/asztal/assets/battery-flash-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/overlays/asztal/assets/chat-bubbles-symbolic.svg b/overlays/asztal/assets/chat-bubbles-symbolic.svg new file mode 100644 index 0000000..fdee0b3 --- /dev/null +++ b/overlays/asztal/assets/chat-bubbles-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/overlays/asztal/assets/controller-symbolic.svg b/overlays/asztal/assets/controller-symbolic.svg new file mode 100644 index 0000000..98bf5d6 --- /dev/null +++ b/overlays/asztal/assets/controller-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/overlays/asztal/assets/controls-symbolic.svg b/overlays/asztal/assets/controls-symbolic.svg new file mode 100644 index 0000000..7df5663 --- /dev/null +++ b/overlays/asztal/assets/controls-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/overlays/asztal/assets/dark-mode-symbolic.svg b/overlays/asztal/assets/dark-mode-symbolic.svg new file mode 100644 index 0000000..9f2e6b4 --- /dev/null +++ b/overlays/asztal/assets/dark-mode-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/overlays/asztal/assets/hourglass-symbolic.svg b/overlays/asztal/assets/hourglass-symbolic.svg new file mode 100644 index 0000000..aa4f97c --- /dev/null +++ b/overlays/asztal/assets/hourglass-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/overlays/asztal/assets/light-mode-symbolic.svg b/overlays/asztal/assets/light-mode-symbolic.svg new file mode 100644 index 0000000..d5fb271 --- /dev/null +++ b/overlays/asztal/assets/light-mode-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/overlays/asztal/assets/mixer-symbolic.svg b/overlays/asztal/assets/mixer-symbolic.svg new file mode 100644 index 0000000..ad6cfa8 --- /dev/null +++ b/overlays/asztal/assets/mixer-symbolic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/overlays/asztal/assets/nix-snowflake-symbolic.svg b/overlays/asztal/assets/nix-snowflake-symbolic.svg new file mode 100644 index 0000000..7bb42ed --- /dev/null +++ b/overlays/asztal/assets/nix-snowflake-symbolic.svg @@ -0,0 +1,155 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/overlays/asztal/assets/preferences-desktop-theme-symbolic.svg b/overlays/asztal/assets/preferences-desktop-theme-symbolic.svg new file mode 100644 index 0000000..4461454 --- /dev/null +++ b/overlays/asztal/assets/preferences-desktop-theme-symbolic.svg @@ -0,0 +1,321 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/overlays/asztal/assets/processor-symbolic.svg b/overlays/asztal/assets/processor-symbolic.svg new file mode 100644 index 0000000..832dbaf --- /dev/null +++ b/overlays/asztal/assets/processor-symbolic.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/overlays/asztal/assets/terminal-symbolic.svg b/overlays/asztal/assets/terminal-symbolic.svg new file mode 100644 index 0000000..9f82bcf --- /dev/null +++ b/overlays/asztal/assets/terminal-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/overlays/asztal/assets/toolbars-symbolic.svg b/overlays/asztal/assets/toolbars-symbolic.svg new file mode 100644 index 0000000..9f4c564 --- /dev/null +++ b/overlays/asztal/assets/toolbars-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/overlays/asztal/config.js b/overlays/asztal/config.js new file mode 100644 index 0000000..2864dec --- /dev/null +++ b/overlays/asztal/config.js @@ -0,0 +1,46 @@ +import GLib from "gi://GLib" + +const main = "/tmp/ags/main.js" +const entry = `${App.configDir}/main.ts` +const bundler = GLib.getenv("AGS_BUNDLER") || "bun" + +const v = { + ags: pkg.version?.split(".").map(Number) || [], + expect: [1, 8, 0], +} + +try { + switch (bundler) { + case "bun": await Utils.execAsync([ + "bun", "build", entry, + "--outfile", main, + "--external", "resource://*", + "--external", "gi://*", + "--external", "file://*", + ]); break + + case "esbuild": await Utils.execAsync([ + "esbuild", "--bundle", entry, + "--format=esm", + `--outfile=${main}`, + "--external:resource://*", + "--external:gi://*", + "--external:file://*", + ]); break + + default: + throw `"${bundler}" is not a valid bundler` + } + + if (v.ags[1] < v.expect[1] || v.ags[2] < v.expect[2]) { + print(`my config needs at least v${v.expect.join(".")}, yours is v${v.ags.join(".")}`) + App.quit() + } + + await import(`file://${main}`) +} catch (error) { + console.error(error) + App.quit() +} + +export { } diff --git a/overlays/asztal/default.nix b/overlays/asztal/default.nix new file mode 100644 index 0000000..b68fa9c --- /dev/null +++ b/overlays/asztal/default.nix @@ -0,0 +1,108 @@ +{ + writeShellScript, + ags, + matugen, + hyprland, + stdenv, + cage, + swww, + esbuild, + dart-sass, + fd, + fzf, + brightnessctl, + gbmonctl, + kitty, + accountsservice, + slurp, + wf-recorder, + wl-clipboard, + wayshot, + swappy, + hyprpicker, + pavucontrol, + networkmanager, + gtk3, + which, +}: +let + name = "asztal"; + + agsPackage = ags.override { extraPackages = [ accountsservice ]; }; + + dependencies = [ + which + dart-sass + fd + fzf + brightnessctl + kitty + gbmonctl + swww + matugen + hyprland + slurp + wf-recorder + wl-clipboard + wayshot + swappy + hyprpicker + pavucontrol + networkmanager + gtk3 + ]; + + addBins = list: builtins.concatStringsSep ":" (builtins.map (p: "${p}/bin") list); + + greeter = writeShellScript "greeter" '' + export PATH=$PATH:${addBins dependencies} + ${cage}/bin/cage -ds -m last ${agsPackage}/bin/ags -- -c ${config}/greeter.js + ''; + + desktop = writeShellScript name '' + export PATH=$PATH:${addBins dependencies} + ${agsPackage}/bin/ags -b ${name} -c ${config}/config.js $@ + ''; + + config = stdenv.mkDerivation { + inherit name; + src = ./.; + + buildPhase = '' + ${esbuild}/bin/esbuild \ + --bundle ./main.ts \ + --outfile=main.js \ + --format=esm \ + --external:resource://\* \ + --external:gi://\* \ + + ${esbuild}/bin/esbuild \ + --bundle ./greeter/greeter.ts \ + --outfile=greeter.js \ + --format=esm \ + --external:resource://\* \ + --external:gi://\* \ + ''; + + installPhase = '' + mkdir -p $out + cp -r assets $out + cp -r style $out + cp -r greeter $out + cp -r widget $out + cp -f main.js $out/config.js + cp -f greeter.js $out/greeter.js + ''; + }; +in +stdenv.mkDerivation { + inherit name; + src = config; + + installPhase = '' + mkdir -p $out/bin + cp -r . $out + cp ${desktop} $out/bin/${name} + cp ${greeter} $out/bin/greeter + ''; +} diff --git a/overlays/asztal/greeter.js b/overlays/asztal/greeter.js new file mode 100644 index 0000000..5c8e369 --- /dev/null +++ b/overlays/asztal/greeter.js @@ -0,0 +1,18 @@ +const main = "/tmp/ags/greeter.js" +const entry = `${App.configDir}/greeter/greeter.ts` + +try { + await Utils.execAsync([ + "bun", "build", entry, + "--outfile", main, + "--external", "resource://*", + "--external", "gi://*", + "--external", "file://*", + ]) + await import(`file://${main}`) +} catch (error) { + console.error(error) + App.quit() +} + +export { } diff --git a/overlays/asztal/greeter/auth.ts b/overlays/asztal/greeter/auth.ts new file mode 100644 index 0000000..23477eb --- /dev/null +++ b/overlays/asztal/greeter/auth.ts @@ -0,0 +1,109 @@ +import AccountsService from "gi://AccountsService?version=1.0" +import GLib from "gi://GLib?version=2.0" +import icons from "lib/icons" + +const { iconFile, realName, userName } = AccountsService.UserManager + .get_default().list_users()[0] + +const loggingin = Variable(false) + +const CMD = GLib.getenv("ASZTAL_DM_CMD") + || "Hyprland" + +const ENV = GLib.getenv("ASZTAL_DM_ENV") + || "WLR_NO_HARDWARE_CURSORS=1 _JAVA_AWT_WM_NONREPARENTING=1" + +async function login(pw: string) { + loggingin.value = true + const greetd = await Service.import("greetd") + return greetd.login(userName, pw, CMD, ENV.split(/\s+/)) + .catch(res => { + loggingin.value = false + response.label = res?.description || JSON.stringify(res) + password.text = "" + revealer.reveal_child = true + }) +} + +const avatar = Widget.Box({ + class_name: "avatar", + hpack: "center", + css: `background-image: url('${iconFile}')`, +}) + +const password = Widget.Entry({ + placeholder_text: "Password", + hexpand: true, + visibility: false, + on_accept: ({ text }) => { login(text || "") }, +}) + +const response = Widget.Label({ + class_name: "response", + wrap: true, + max_width_chars: 35, + hpack: "center", + hexpand: true, + xalign: .5, +}) + +const revealer = Widget.Revealer({ + transition: "slide_down", + child: response, +}) + +export default Widget.Box({ + class_name: "auth", + attribute: { password }, + vertical: true, + children: [ + Widget.Overlay({ + child: Widget.Box( + { + css: "min-width: 200px; min-height: 200px;", + vertical: true, + }, + Widget.Box({ + class_name: "wallpaper", + css: `background-image: url('${WALLPAPER}')`, + }), + Widget.Box({ + class_name: "wallpaper-contrast", + vexpand: true, + }), + ), + overlay: Widget.Box( + { + vpack: "end", + vertical: true, + }, + avatar, + Widget.Box({ + hpack: "center", + children: [ + Widget.Icon(icons.ui.avatar), + Widget.Label(realName || userName), + ], + }), + Widget.Box( + { + class_name: "password", + }, + Widget.Spinner({ + visible: loggingin.bind(), + active: true, + }), + Widget.Icon({ + visible: loggingin.bind().as(b => !b), + icon: icons.ui.lock, + }), + password, + ), + ), + }), + Widget.Box( + { class_name: "response-box" }, + revealer, + ), + ], +}) diff --git a/overlays/asztal/greeter/greeter.scss b/overlays/asztal/greeter/greeter.scss new file mode 100644 index 0000000..e3a5cd8 --- /dev/null +++ b/overlays/asztal/greeter/greeter.scss @@ -0,0 +1,64 @@ +@import "../style/mixins/floating-widget.scss"; +@import "../style/mixins/widget.scss"; +@import "../style/mixins/spacing.scss"; +@import "../style/mixins/unset.scss"; +@import "../style/mixins/a11y-button.scss"; +@import "../widget/bar/bar.scss"; + +window#greeter { + background-color: lighten($bg, 6%); + color: $fg; + + .bar { + background-color: transparent; + + .date { + @include unset($rec: true); + @include panel-button($flat: true, $reactive: false); + } + } + + .auth { + @include floating_widget; + border-radius: $radius; + min-width: 400px; + padding: 0; + + .wallpaper { + min-height: 220px; + background-size: cover; + border-top-left-radius: $radius; + border-top-right-radius: $radius; + } + + .wallpaper-contrast { + min-height: 100px; + } + + .avatar { + border-radius: 99px; + min-width: 140px; + min-height: 140px; + background-size: cover; + box-shadow: 3px 3px 6px 0 $shadow-color; + margin-bottom: $spacing; + } + + + .password { + entry { + @include button; + padding: $padding*.7 $padding; + margin-left: $spacing*.5; + } + + margin: 0 $padding*4; + margin-top: $spacing; + } + + .response-box { + color: $error-bg; + margin: $spacing 0; + } + } +} diff --git a/overlays/asztal/greeter/greeter.ts b/overlays/asztal/greeter/greeter.ts new file mode 100644 index 0000000..eb1493f --- /dev/null +++ b/overlays/asztal/greeter/greeter.ts @@ -0,0 +1,37 @@ +import "./session" +import "style/style" +import GLib from "gi://GLib?version=2.0" +import RegularWindow from "widget/RegularWindow" +import statusbar from "./statusbar" +import auth from "./auth" + +const win = RegularWindow({ + name: "greeter", + setup: self => { + self.set_default_size(500, 500) + self.show_all() + auth.attribute.password.grab_focus() + }, + child: Widget.Overlay({ + child: Widget.Box({ expand: true }), + overlays: [ + Widget.Box({ + vpack: "start", + hpack: "fill", + hexpand: true, + child: statusbar, + }), + Widget.Box({ + vpack: "center", + hpack: "center", + child: auth, + }), + ], + }), +}) + +App.config({ + icons: "./assets", + windows: [win], + cursorTheme: GLib.getenv("XCURSOR_THEME")!, +}) diff --git a/overlays/asztal/greeter/session.ts b/overlays/asztal/greeter/session.ts new file mode 100644 index 0000000..092a5c2 --- /dev/null +++ b/overlays/asztal/greeter/session.ts @@ -0,0 +1,20 @@ +import GLib from "gi://GLib?version=2.0" +import AccountsService from "gi://AccountsService?version=1.0" + +const { userName } = AccountsService.UserManager.get_default().list_users()[0] + +declare global { + const WALLPAPER: string +} + +Object.assign(globalThis, { + TMP: `${GLib.get_tmp_dir()}/greeter`, + OPTIONS: "/var/cache/greeter/options.json", + WALLPAPER: "/var/cache/greeter/background", + // TMP: "/tmp/ags", + // OPTIONS: Utils.CACHE_DIR + "/options.json", + // WALLPAPER: Utils.HOME + "/.config/background", + USER: userName, +}) + +Utils.ensureDirectory(TMP) diff --git a/overlays/asztal/greeter/statusbar.ts b/overlays/asztal/greeter/statusbar.ts new file mode 100644 index 0000000..8076011 --- /dev/null +++ b/overlays/asztal/greeter/statusbar.ts @@ -0,0 +1,46 @@ +import { clock } from "lib/variables" +import options from "options" +import icons from "lib/icons" +import BatteryBar from "widget/bar/buttons/BatteryBar" +import PanelButton from "widget/bar/PanelButton" + +const { scheme } = options.theme +const { monochrome } = options.bar.powermenu +const { format } = options.bar.date + +const poweroff = PanelButton({ + class_name: "powermenu", + child: Widget.Icon(icons.powermenu.shutdown), + on_clicked: () => Utils.exec("shutdown now"), + setup: self => self.hook(monochrome, () => { + self.toggleClassName("colored", !monochrome.value) + self.toggleClassName("box") + }), +}) + +const date = PanelButton({ + class_name: "date", + child: Widget.Label({ + label: clock.bind().as(c => c.format(`${format}`)!), + }), +}) + +const darkmode = PanelButton({ + class_name: "darkmode", + child: Widget.Icon({ icon: scheme.bind().as(s => icons.color[s]) }), + on_clicked: () => scheme.value = scheme.value === "dark" ? "light" : "dark", +}) + +export default Widget.CenterBox({ + class_name: "bar", + hexpand: true, + center_widget: date, + end_widget: Widget.Box({ + hpack: "end", + children: [ + darkmode, + BatteryBar(), + poweroff, + ], + }), +}) diff --git a/overlays/asztal/lib/battery.ts b/overlays/asztal/lib/battery.ts new file mode 100644 index 0000000..3817260 --- /dev/null +++ b/overlays/asztal/lib/battery.ts @@ -0,0 +1,16 @@ +import icons from "./icons" + +export default async function init() { + const bat = await Service.import("battery") + bat.connect("notify::percent", ({ percent, charging }) => { + const low = 30 + if (percent !== low || percent !== low / 2 || !charging) + return + + Utils.notify({ + summary: `${percent}% Battery Percentage`, + iconName: icons.battery.warning, + urgency: "critical", + }) + }) +} diff --git a/overlays/asztal/lib/gtk.ts b/overlays/asztal/lib/gtk.ts new file mode 100644 index 0000000..8cd60a3 --- /dev/null +++ b/overlays/asztal/lib/gtk.ts @@ -0,0 +1,16 @@ +import Gio from "gi://Gio" +import options from "options" + +const settings = new Gio.Settings({ + schema: "org.gnome.desktop.interface", +}) + +function gtk() { + const scheme = options.theme.scheme.value + settings.set_string("color-scheme", `prefer-${scheme}`) +} + +export default function init() { + options.theme.scheme.connect("changed", gtk) + gtk() +} diff --git a/overlays/asztal/lib/hyprland.ts b/overlays/asztal/lib/hyprland.ts new file mode 100644 index 0000000..b121ed7 --- /dev/null +++ b/overlays/asztal/lib/hyprland.ts @@ -0,0 +1,80 @@ +import options from "options" +const { messageAsync } = await Service.import("hyprland") + +const { + hyprland, + theme: { + spacing, + radius, + border: { width }, + blur, + shadows, + dark: { + primary: { bg: darkActive }, + }, + light: { + primary: { bg: lightActive }, + }, + scheme, + }, +} = options + +const deps = [ + "hyprland", + spacing.id, + radius.id, + blur.id, + width.id, + shadows.id, + darkActive.id, + lightActive.id, + scheme.id, +] + +function activeBorder() { + const color = scheme.value === "dark" + ? darkActive.value + : lightActive.value + + return color.replace("#", "") +} + +function sendBatch(batch: string[]) { + const cmd = batch + .filter(x => !!x) + .map(x => `keyword ${x}`) + .join("; ") + + return messageAsync(`[[BATCH]]/${cmd}`) +} + +async function setupHyprland() { + const wm_gaps = Math.floor(hyprland.gaps.value * spacing.value) + + sendBatch([ + `general:border_size ${width}`, + `general:gaps_out ${wm_gaps}`, + `general:gaps_in ${Math.floor(wm_gaps / 2)}`, + `general:col.active_border rgba(${activeBorder()}ff)`, + `general:col.inactive_border rgba(${hyprland.inactiveBorder.value})`, + `decoration:rounding ${radius}`, + `decoration:drop_shadow ${shadows.value ? "yes" : "no"}`, + `dwindle:no_gaps_when_only ${hyprland.gapsWhenOnly.value ? 0 : 1}`, + `master:no_gaps_when_only ${hyprland.gapsWhenOnly.value ? 0 : 1}`, + ]) + + await sendBatch(App.windows.map(({ name }) => `layerrule unset, ${name}`)) + + if (blur.value > 0) { + sendBatch(App.windows.flatMap(({ name }) => [ + `layerrule unset, ${name}`, + `layerrule blur, ${name}`, + `layerrule ignorealpha ${/* based on shadow color */.29}, ${name}`, + ])) + } +} + +export default function init() { + options.handler(deps, setupHyprland) + setupHyprland() +} diff --git a/overlays/asztal/lib/icons.ts b/overlays/asztal/lib/icons.ts new file mode 100644 index 0000000..f6da697 --- /dev/null +++ b/overlays/asztal/lib/icons.ts @@ -0,0 +1,145 @@ +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", +} + +export default { + missing: "image-missing-symbolic", + nix: { + nix: "nix-snowflake-symbolic", + }, + app: { + terminal: "terminal-symbolic", + }, + fallback: { + executable: "application-x-executable", + notification: "dialog-information-symbolic", + video: "video-x-generic-symbolic", + audio: "audio-x-generic-symbolic", + }, + ui: { + close: "window-close-symbolic", + colorpicker: "color-select-symbolic", + info: "info-symbolic", + link: "external-link-symbolic", + lock: "system-lock-screen-symbolic", + menu: "open-menu-symbolic", + refresh: "view-refresh-symbolic", + search: "system-search-symbolic", + settings: "emblem-system-symbolic", + themes: "preferences-desktop-theme-symbolic", + tick: "object-select-symbolic", + time: "hourglass-symbolic", + toolbars: "toolbars-symbolic", + warning: "dialog-warning-symbolic", + avatar: "avatar-default-symbolic", + arrow: { + right: "pan-end-symbolic", + left: "pan-start-symbolic", + down: "pan-down-symbolic", + up: "pan-up-symbolic", + }, + }, + audio: { + mic: { + muted: "microphone-disabled-symbolic", + low: "microphone-sensitivity-low-symbolic", + medium: "microphone-sensitivity-medium-symbolic", + high: "microphone-sensitivity-high-symbolic", + }, + volume: { + muted: "audio-volume-muted-symbolic", + low: "audio-volume-low-symbolic", + medium: "audio-volume-medium-symbolic", + high: "audio-volume-high-symbolic", + overamplified: "audio-volume-overamplified-symbolic", + }, + type: { + headset: "audio-headphones-symbolic", + speaker: "audio-speakers-symbolic", + card: "audio-card-symbolic", + }, + mixer: "mixer-symbolic", + }, + powerprofile: { + balanced: "power-profile-balanced-symbolic", + "power-saver": "power-profile-power-saver-symbolic", + performance: "power-profile-performance-symbolic", + }, + asusctl: { + profile: { + Balanced: "power-profile-balanced-symbolic", + Quiet: "power-profile-power-saver-symbolic", + Performance: "power-profile-performance-symbolic", + }, + mode: { + Integrated: "processor-symbolic", + Hybrid: "controller-symbolic", + }, + }, + battery: { + charging: "battery-flash-symbolic", + warning: "battery-empty-symbolic", + }, + bluetooth: { + enabled: "bluetooth-active-symbolic", + disabled: "bluetooth-disabled-symbolic", + }, + brightness: { + indicator: "display-brightness-symbolic", + keyboard: "keyboard-brightness-symbolic", + screen: "display-brightness-symbolic", + }, + powermenu: { + sleep: "weather-clear-night-symbolic", + reboot: "system-reboot-symbolic", + logout: "system-log-out-symbolic", + shutdown: "system-shutdown-symbolic", + }, + recorder: { + recording: "media-record-symbolic", + }, + notifications: { + noisy: "org.gnome.Settings-notifications-symbolic", + silent: "notifications-disabled-symbolic", + message: "chat-bubbles-symbolic", + }, + trash: { + full: "user-trash-full-symbolic", + empty: "user-trash-symbolic", + }, + mpris: { + shuffle: { + enabled: "media-playlist-shuffle-symbolic", + disabled: "media-playlist-consecutive-symbolic", + }, + loop: { + none: "media-playlist-repeat-symbolic", + track: "media-playlist-repeat-song-symbolic", + playlist: "media-playlist-repeat-symbolic", + }, + playing: "media-playback-pause-symbolic", + paused: "media-playback-start-symbolic", + stopped: "media-playback-start-symbolic", + prev: "media-skip-backward-symbolic", + next: "media-skip-forward-symbolic", + }, + system: { + cpu: "org.gnome.SystemMonitor-symbolic", + ram: "drive-harddisk-solidstate-symbolic", + temp: "temperature-symbolic", + }, + color: { + dark: "dark-mode-symbolic", + light: "light-mode-symbolic", + }, +} diff --git a/overlays/asztal/lib/init.ts b/overlays/asztal/lib/init.ts new file mode 100644 index 0000000..aa03300 --- /dev/null +++ b/overlays/asztal/lib/init.ts @@ -0,0 +1,19 @@ +import matugen from "./matugen" +import hyprland from "./hyprland" +import tmux from "./tmux" +import gtk from "./gtk" +import lowBattery from "./battery" +import notifications from "./notifications" + +export default function init() { + try { + gtk() + tmux() + matugen() + lowBattery() + notifications() + hyprland() + } catch (error) { + logError(error) + } +} diff --git a/overlays/asztal/lib/matugen.ts b/overlays/asztal/lib/matugen.ts new file mode 100644 index 0000000..dfccccf --- /dev/null +++ b/overlays/asztal/lib/matugen.ts @@ -0,0 +1,113 @@ +import wallpaper from "service/wallpaper" +import options from "options" +import { sh, dependencies } from "./utils" + +export default function init() { + wallpaper.connect("changed", () => matugen()) + options.autotheme.connect("changed", () => matugen()) +} + +function animate(...setters: Array<() => void>) { + const delay = options.transition.value / 2 + setters.forEach((fn, i) => Utils.timeout(delay * i, fn)) +} + +export async function matugen( + type: "image" | "color" = "image", + arg = wallpaper.wallpaper, +) { + if (!options.autotheme.value || !dependencies("matugen")) + return + + const colors = await sh(`matugen --dry-run -j hex ${type} ${arg}`) + const c = JSON.parse(colors).colors as { light: Colors, dark: Colors } + const { dark, light } = options.theme + + animate( + () => { + dark.widget.value = c.dark.on_surface + light.widget.value = c.light.on_surface + }, + () => { + dark.border.value = c.dark.outline + light.border.value = c.light.outline + }, + () => { + dark.bg.value = c.dark.surface + light.bg.value = c.light.surface + }, + () => { + dark.fg.value = c.dark.on_surface + light.fg.value = c.light.on_surface + }, + () => { + dark.primary.bg.value = c.dark.primary + light.primary.bg.value = c.light.primary + options.bar.battery.charging.value = options.theme.scheme.value === "dark" + ? c.dark.primary : c.light.primary + }, + () => { + dark.primary.fg.value = c.dark.on_primary + light.primary.fg.value = c.light.on_primary + }, + () => { + dark.error.bg.value = c.dark.error + light.error.bg.value = c.light.error + }, + () => { + dark.error.fg.value = c.dark.on_error + light.error.fg.value = c.light.on_error + }, + ) +} + +type Colors = { + background: string + error: string + error_container: string + inverse_on_surface: string + inverse_primary: string + inverse_surface: string + on_background: string + on_error: string + on_error_container: string + on_primary: string + on_primary_container: string + on_primary_fixed: string + on_primary_fixed_variant: string + on_secondary: string + on_secondary_container: string + on_secondary_fixed: string + on_secondary_fixed_variant: string + on_surface: string + on_surface_variant: string + on_tertiary: string + on_tertiary_container: string + on_tertiary_fixed: string + on_tertiary_fixed_variant: string + outline: string + outline_variant: string + primary: string + primary_container: string + primary_fixed: string + primary_fixed_dim: string + scrim: string + secondary: string + secondary_container: string + secondary_fixed: string + secondary_fixed_dim: string + shadow: string + surface: string + surface_bright: string + surface_container: string + surface_container_high: string + surface_container_highest: string + surface_container_low: string + surface_container_lowest: string + surface_dim: string + surface_variant: string + tertiary: string + tertiary_container: string + tertiary_fixed: string + tertiary_fixed_dim: string +} diff --git a/overlays/asztal/lib/notifications.ts b/overlays/asztal/lib/notifications.ts new file mode 100644 index 0000000..0000831 --- /dev/null +++ b/overlays/asztal/lib/notifications.ts @@ -0,0 +1,16 @@ +import options from "options" +const notifs = await Service.import("notifications") + +// TODO: consider adding this to upstream + +const { blacklist } = options.notifications + +export default function init() { + const notify = notifs.constructor.prototype.Notify.bind(notifs) + notifs.constructor.prototype.Notify = function(appName: string, ...rest: unknown[]) { + if (blacklist.value.includes(appName)) + return Number.MAX_SAFE_INTEGER + + return notify(appName, ...rest) + } +} diff --git a/overlays/asztal/lib/option.ts b/overlays/asztal/lib/option.ts new file mode 100644 index 0000000..2d73978 --- /dev/null +++ b/overlays/asztal/lib/option.ts @@ -0,0 +1,115 @@ +import { Variable } from "resource:///com/github/Aylur/ags/variable.js" + +type OptProps = { + persistent?: boolean +} + +export class Opt extends Variable { + static { Service.register(this) } + + constructor(initial: T, { persistent = false }: OptProps = {}) { + super(initial) + this.initial = initial + this.persistent = persistent + } + + initial: T + id = "" + persistent: boolean + toString() { return `${this.value}` } + toJSON() { return `opt:${this.value}` } + + getValue = (): T => { + return super.getValue() + } + + init(cacheFile: string) { + const cacheV = JSON.parse(Utils.readFile(cacheFile) || "{}")[this.id] + if (cacheV !== undefined) + this.value = cacheV + + this.connect("changed", () => { + const cache = JSON.parse(Utils.readFile(cacheFile) || "{}") + cache[this.id] = this.value + Utils.writeFileSync(JSON.stringify(cache, null, 2), cacheFile) + }) + } + + reset() { + if (this.persistent) + return + + if (JSON.stringify(this.value) !== JSON.stringify(this.initial)) { + this.value = this.initial + return this.id + } + } +} + +export const opt = (initial: T, opts?: OptProps) => new Opt(initial, opts) + +function getOptions(object: object, path = ""): Opt[] { + return Object.keys(object).flatMap(key => { + const obj: Opt = object[key] + const id = path ? path + "." + key : key + + if (obj instanceof Variable) { + obj.id = id + return obj + } + + if (typeof obj === "object") + return getOptions(obj, id) + + return [] + }) +} + +export function mkOptions(cacheFile: string, object: T) { + for (const opt of getOptions(object)) + opt.init(cacheFile) + + Utils.ensureDirectory(cacheFile.split("/").slice(0, -1).join("/")) + + const configFile = `${TMP}/config.json` + const values = getOptions(object).reduce((obj, { id, value }) => ({ [id]: value, ...obj }), {}) + Utils.writeFileSync(JSON.stringify(values, null, 2), configFile) + Utils.monitorFile(configFile, () => { + const cache = JSON.parse(Utils.readFile(configFile) || "{}") + for (const opt of getOptions(object)) { + if (JSON.stringify(cache[opt.id]) !== JSON.stringify(opt.value)) + opt.value = cache[opt.id] + } + }) + + function sleep(ms = 0) { + return new Promise(r => setTimeout(r, ms)) + } + + async function reset( + [opt, ...list] = getOptions(object), + id = opt?.reset(), + ): Promise> { + if (!opt) + return sleep().then(() => []) + + return id + ? [id, ...(await sleep(50).then(() => reset(list)))] + : await sleep().then(() => reset(list)) + } + + return Object.assign(object, { + configFile, + array: () => getOptions(object), + async reset() { + return (await reset()).join("\n") + }, + handler(deps: string[], callback: () => void) { + for (const opt of getOptions(object)) { + if (deps.some(i => opt.id.startsWith(i))) + opt.connect("changed", callback) + } + }, + }) +} + diff --git a/overlays/asztal/lib/session.ts b/overlays/asztal/lib/session.ts new file mode 100644 index 0000000..0e3e0cf --- /dev/null +++ b/overlays/asztal/lib/session.ts @@ -0,0 +1,16 @@ +import GLib from "gi://GLib?version=2.0" + +declare global { + const OPTIONS: string + const TMP: string + const USER: string +} + +Object.assign(globalThis, { + OPTIONS: `${GLib.get_user_cache_dir()}/ags/options.json`, + TMP: `${GLib.get_tmp_dir()}/asztal`, + USER: GLib.get_user_name(), +}) + +Utils.ensureDirectory(TMP) +App.addIcons(`${App.configDir}/assets`) diff --git a/overlays/asztal/lib/tmux.ts b/overlays/asztal/lib/tmux.ts new file mode 100644 index 0000000..1372eb2 --- /dev/null +++ b/overlays/asztal/lib/tmux.ts @@ -0,0 +1,14 @@ +import options from "options" +import { sh } from "./utils" + +export async function tmux() { + const { scheme, dark, light } = options.theme + const hex = scheme.value === "dark" ? dark.primary.bg.value : light.primary.bg.value + if (await sh("which tmux")) + sh(`tmux set @main_accent "${hex}"`) +} + +export default function init() { + options.theme.dark.primary.bg.connect("changed", tmux) + options.theme.light.primary.bg.connect("changed", tmux) +} diff --git a/overlays/asztal/lib/utils.ts b/overlays/asztal/lib/utils.ts new file mode 100644 index 0000000..9b9d2a1 --- /dev/null +++ b/overlays/asztal/lib/utils.ts @@ -0,0 +1,111 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { type Application } from "types/service/applications" +import icons, { substitutes } from "./icons" +import Gtk from "gi://Gtk?version=3.0" +import Gdk from "gi://Gdk" +import GLib from "gi://GLib?version=2.0" + +export type Binding = import("types/service").Binding + +/** + * @returns substitute icon || name || fallback icon + */ +export function icon(name: string | null, fallback = icons.missing) { + if (!name) + return fallback || "" + + if (GLib.file_test(name, GLib.FileTest.EXISTS)) + return name + + const icon = (substitutes[name] || name) + if (Utils.lookUpIcon(icon)) + return icon + + print(`no icon substitute "${icon}" for "${name}", fallback: "${fallback}"`) + return fallback +} + +/** + * @returns execAsync(["bash", "-c", cmd]) + */ +export async function bash(strings: TemplateStringsArray | string, ...values: unknown[]) { + const cmd = typeof strings === "string" ? strings : strings + .flatMap((str, i) => str + `${values[i] ?? ""}`) + .join("") + + return Utils.execAsync(["bash", "-c", cmd]).catch(err => { + console.error(cmd, err) + return "" + }) +} + +/** + * @returns execAsync(cmd) + */ +export async function sh(cmd: string | string[]) { + return Utils.execAsync(cmd).catch(err => { + console.error(typeof cmd === "string" ? cmd : cmd.join(" "), err) + return "" + }) +} + +export function forMonitors(widget: (monitor: number) => Gtk.Window) { + const n = Gdk.Display.get_default()?.get_n_monitors() || 1 + return range(n, 0).map(widget).flat(1) +} + +/** + * @returns [start...length] + */ +export function range(length: number, start = 1) { + return Array.from({ length }, (_, i) => i + start) +} + +/** + * @returns true if all of the `bins` are found + */ +export function dependencies(...bins: string[]) { + const missing = bins.filter(bin => { + return !Utils.exec(`which ${bin}`) + }) + + if (missing.length > 0) { + console.warn("missing dependencies:", missing.join(", ")) + Utils.notify(`missing dependencies: ${missing.join(", ")}`) + } + + return missing.length === 0 +} + +/** + * run app detached + */ +export function launchApp(app: Application) { + const exe = app.executable + .split(/\s+/) + .filter(str => !str.startsWith("%") && !str.startsWith("@")) + .join(" ") + + bash(`${exe} &`) + app.frequency += 1 +} + +/** + * to use with drag and drop + */ +export function createSurfaceFromWidget(widget: Gtk.Widget) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const cairo = imports.gi.cairo as any + const alloc = widget.get_allocation() + const surface = new cairo.ImageSurface( + cairo.Format.ARGB32, + alloc.width, + alloc.height, + ) + const cr = new cairo.Context(surface) + cr.setSourceRGBA(255, 255, 255, 0) + cr.rectangle(0, 0, alloc.width, alloc.height) + cr.fill() + widget.draw(cr) + return surface +} diff --git a/overlays/asztal/lib/variables.ts b/overlays/asztal/lib/variables.ts new file mode 100644 index 0000000..19308cd --- /dev/null +++ b/overlays/asztal/lib/variables.ts @@ -0,0 +1,42 @@ +import GLib from "gi://GLib" +// import options from "options" +// +// const intval = options.system.fetchInterval.value +// const tempPath = options.system.temperature.value + +export const clock = Variable(GLib.DateTime.new_now_local(), { + poll: [1000, () => GLib.DateTime.new_now_local()], +}) + +export const uptime = Variable(0, { + poll: [60_000, "cat /proc/uptime", line => + Number.parseInt(line.split(".")[0]) / 60, + ], +}) + +export const distro = { + id: GLib.get_os_info("ID"), + logo: GLib.get_os_info("LOGO"), +} + +// const divide = ([total, free]: string[]) => Number.parseInt(free) / Number.parseInt(total) +// +// export const cpu = Variable(0, { +// poll: [intval, "top -b -n 1", out => divide(["100", out.split("\n") +// .find(line => line.includes("Cpu(s)")) +// ?.split(/\s+/)[1] +// .replace(",", ".") || "0"])], +// }) +// +// export const ram = Variable(0, { +// poll: [intval, "free", out => divide(out.split("\n") +// .find(line => line.includes("Mem:")) +// ?.split(/\s+/) +// .splice(1, 2) || ["1", "1"])], +// }) +// +// export const temperature = Variable(0, { +// poll: [intval, `cat ${tempPath}`, n => { +// return Number.parseInt(n) / 100_000 +// }], +// }) diff --git a/overlays/asztal/main.ts b/overlays/asztal/main.ts new file mode 100644 index 0000000..2c0ab2d --- /dev/null +++ b/overlays/asztal/main.ts @@ -0,0 +1,41 @@ +import "lib/session" +import "style/style" +import init from "lib/init" +import options from "options" +import Bar from "widget/bar/Bar" +import Launcher from "widget/launcher/Launcher" +import NotificationPopups from "widget/notifications/NotificationPopups" +import OSD from "widget/osd/OSD" +import Overview from "widget/overview/Overview" +import PowerMenu from "widget/powermenu/PowerMenu" +import ScreenCorners from "widget/bar/ScreenCorners" +import SettingsDialog from "widget/settings/SettingsDialog" +import Verification from "widget/powermenu/Verification" +import { forMonitors } from "lib/utils" +import { setupQuickSettings } from "widget/quicksettings/QuickSettings" +import { setupDateMenu } from "widget/datemenu/DateMenu" + +App.config({ + onConfigParsed: () => { + setupQuickSettings() + setupDateMenu() + init() + }, + closeWindowDelay: { + "launcher": options.transition.value, + "overview": options.transition.value, + "quicksettings": options.transition.value, + "datemenu": options.transition.value, + }, + windows: () => [ + ...forMonitors(Bar), + ...forMonitors(NotificationPopups), + ...forMonitors(ScreenCorners), + ...forMonitors(OSD), + Launcher(), + Overview(), + PowerMenu(), + SettingsDialog(), + Verification(), + ], +}) diff --git a/overlays/asztal/options.ts b/overlays/asztal/options.ts new file mode 100644 index 0000000..fce288e --- /dev/null +++ b/overlays/asztal/options.ts @@ -0,0 +1,243 @@ +import { opt, mkOptions } from "lib/option" +import { distro } from "lib/variables" +import { icon } from "lib/utils" +import icons from "lib/icons" + +const options = mkOptions(OPTIONS, { + autotheme: opt(false), + + wallpaper: { + resolution: opt(1920), + market: opt("random"), + }, + + theme: { + dark: { + primary: { + bg: opt("#51a4e7"), + fg: opt("#141414"), + }, + error: { + bg: opt("#e55f86"), + fg: opt("#141414"), + }, + bg: opt("#171717"), + fg: opt("#eeeeee"), + widget: opt("#eeeeee"), + border: opt("#eeeeee"), + }, + light: { + primary: { + bg: opt("#426ede"), + fg: opt("#eeeeee"), + }, + error: { + bg: opt("#b13558"), + fg: opt("#eeeeee"), + }, + bg: opt("#fffffa"), + fg: opt("#080808"), + widget: opt("#080808"), + border: opt("#080808"), + }, + + blur: opt(0), + scheme: opt<"dark" | "light">("dark"), + widget: { opacity: opt(94) }, + border: { + width: opt(1), + opacity: opt(96), + }, + + shadows: opt(true), + padding: opt(7), + spacing: opt(12), + radius: opt(11), + }, + + transition: opt(200), + + font: { + size: opt(13), + name: opt("Ubuntu Nerd Font"), + }, + + bar: { + flatButtons: opt(true), + position: opt<"top" | "bottom">("top"), + corners: opt(true), + layout: { + start: opt>([ + "launcher", + "workspaces", + "taskbar", + "expander", + "messages", + ]), + center: opt>([ + "date", + ]), + end: opt>([ + "media", + "expander", + "systray", + "colorpicker", + "screenrecord", + "system", + "battery", + "powermenu", + ]), + }, + launcher: { + icon: { + colored: opt(true), + icon: opt(icon(distro.logo, icons.ui.search)), + }, + label: { + colored: opt(false), + label: opt(" Applications"), + }, + action: opt(() => App.toggleWindow("launcher")), + }, + date: { + format: opt("%H:%M - %A %e."), + action: opt(() => App.toggleWindow("datemenu")), + }, + battery: { + bar: opt<"hidden" | "regular" | "whole">("regular"), + charging: opt("#00D787"), + percentage: opt(true), + blocks: opt(7), + width: opt(50), + low: opt(30), + }, + workspaces: { + workspaces: opt(7), + }, + taskbar: { + iconSize: opt(0), + monochrome: opt(true), + exclusive: opt(false), + }, + messages: { + action: opt(() => App.toggleWindow("datemenu")), + }, + systray: { + ignore: opt([ + "KDE Connect Indicator", + "spotify-client", + ]), + }, + media: { + monochrome: opt(true), + preferred: opt("spotify"), + direction: opt<"left" | "right">("right"), + format: opt("{artists} - {title}"), + length: opt(40), + }, + powermenu: { + monochrome: opt(false), + action: opt(() => App.toggleWindow("powermenu")), + }, + }, + + launcher: { + width: opt(0), + margin: opt(80), + nix: { + pkgs: opt("nixpkgs/nixos-unstable"), + max: opt(8), + }, + sh: { + max: opt(16), + }, + apps: { + iconSize: opt(62), + max: opt(6), + favorites: opt([ + [ + "firefox", + "org.gnome.Nautilus", + "org.gnome.Calendar", + "obsidian", + "discord", + "spotify", + ], + ]), + }, + }, + + overview: { + scale: opt(9), + workspaces: opt(7), + monochromeIcon: opt(true), + }, + + powermenu: { + sleep: opt("systemctl suspend"), + reboot: opt("systemctl reboot"), + logout: opt("pkill Hyprland"), + shutdown: opt("shutdown now"), + layout: opt<"line" | "box">("line"), + labels: opt(true), + }, + + quicksettings: { + avatar: { + image: opt(`/var/lib/AccountsService/icons/${Utils.USER}`), + size: opt(70), + }, + width: opt(380), + position: opt<"left" | "center" | "right">("right"), + networkSettings: opt("gtk-launch gnome-control-center"), + media: { + monochromeIcon: opt(true), + coverSize: opt(100), + }, + }, + + datemenu: { + position: opt<"left" | "center" | "right">("center"), + weather: { + interval: opt(60_000), + unit: opt<"metric" | "imperial" | "standard">("metric"), + key: opt( + JSON.parse(Utils.readFile(`${App.configDir}/.weather`) || "{}")?.key || "", + ), + cities: opt>( + JSON.parse(Utils.readFile(`${App.configDir}/.weather`) || "{}")?.cities || [], + ), + }, + }, + + osd: { + progress: { + vertical: opt(true), + pack: { + h: opt<"start" | "center" | "end">("end"), + v: opt<"start" | "center" | "end">("center"), + }, + }, + microphone: { + pack: { + h: opt<"start" | "center" | "end">("center"), + v: opt<"start" | "center" | "end">("end"), + }, + }, + }, + + notifications: { + position: opt>(["top", "right"]), + blacklist: opt(["Spotify"]), + width: opt(440), + }, + + hyprland: { + gaps: opt(2.4), + inactiveBorder: opt("333333ff"), + gapsWhenOnly: opt(false), + }, +}) + +globalThis["options"] = options +export default options diff --git a/overlays/asztal/package.json b/overlays/asztal/package.json new file mode 100644 index 0000000..bc86b1b --- /dev/null +++ b/overlays/asztal/package.json @@ -0,0 +1,19 @@ +{ + "name": "ags-dotfiles", + "author": "Aylur", + "kofi": "https://ko-fi.com/aylur", + "repository": { + "type": "git", + "url": "git+https://github.com/Aylur/dotfiles.git" + }, + "devDependencies": { + "@girs/accountsservice-1.0": "^1.0.0-3.2.7", + "@typescript-eslint/eslint-plugin": "^6.20.0", + "eslint": "^8.56.0", + "eslint-config-standard-with-typescript": "^43.0.1", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-n": "^16.6.2", + "eslint-plugin-promise": "^6.1.1", + "typescript": "^5.3.3" + } +} diff --git a/overlays/asztal/service/asusctl.ts b/overlays/asztal/service/asusctl.ts new file mode 100644 index 0000000..16acbd7 --- /dev/null +++ b/overlays/asztal/service/asusctl.ts @@ -0,0 +1,52 @@ +import { sh } from "lib/utils" + +type Profile = "Performance" | "Balanced" | "Quiet" +type Mode = "Hybrid" | "Integrated" + +class Asusctl extends Service { + static { + Service.register(this, {}, { + "profile": ["string", "r"], + "mode": ["string", "r"], + }) + } + + available = !!Utils.exec("which asusctl") + #profile: Profile = "Balanced" + #mode: Mode = "Hybrid" + + async nextProfile() { + await sh("asusctl profile -n") + const profile = await sh("asusctl profile -p") + const p = profile.split(" ")[3] as Profile + this.#profile = p + this.changed("profile") + } + + async setProfile(prof: Profile) { + await sh(`asusctl profile --profile-set ${prof}`) + this.#profile = prof + this.changed("profile") + } + + async nextMode() { + await sh(`supergfxctl -m ${this.#mode === "Hybrid" ? "Integrated" : "Hybrid"}`) + this.#mode = await sh("supergfxctl -g") as Mode + this.changed("profile") + } + + constructor() { + super() + + if (this.available) { + sh("asusctl profile -p").then(p => this.#profile = p.split(" ")[3] as Profile) + sh("supergfxctl -g").then(m => this.#mode = m as Mode) + } + } + + get profiles(): Profile[] { return ["Performance", "Balanced", "Quiet"] } + get profile() { return this.#profile } + get mode() { return this.#mode } +} + +export default new Asusctl diff --git a/overlays/asztal/service/brightness.ts b/overlays/asztal/service/brightness.ts new file mode 100644 index 0000000..425ce69 --- /dev/null +++ b/overlays/asztal/service/brightness.ts @@ -0,0 +1,78 @@ +import { bash, dependencies, sh } from "lib/utils"; + +if (!dependencies("brightnessctl")) App.quit(); + +const get = (args: string) => Number(Utils.exec(`brightnessctl ${args}`)); +const screen = await bash`ls -w1 /sys/class/backlight | head -1`; +const kbd = await bash`ls -w1 /sys/class/leds | head -1`; + +class Brightness extends Service { + static { + Service.register( + this, + {}, + { + screen: ["float", "rw"], + kbd: ["int", "rw"], + }, + ); + } + + #kbdMax = get(`--device ${kbd} max`); + #kbd = get(`--device ${kbd} get`); + #screenMax = get("max"); + #screen = get("get") / get("max"); + + get kbd() { + return this.#kbd; + } + get screen() { + return this.#screen; + } + + set kbd(value) { + if (value < 0 || value > this.#kbdMax) return; + + sh(`brightnessctl -d ${kbd} s ${value} -q`).then(() => { + this.#kbd = value; + this.changed("kbd"); + }); + } + + set screen(percent) { + if (percent < 0) percent = 0; + + if (percent > 1) percent = 1; + + sh( + `gbmonctl --prop brightness -val ${Math.min( + Math.max(Math.floor(percent * 100), 0), + 100, + )}`, + ).then(() => { + this.#screen = percent; + this.changed("screen"); + }); + } + + constructor() { + super(); + + const screenPath = `/sys/class/backlight/${screen}/brightness`; + const kbdPath = `/sys/class/leds/${kbd}/brightness`; + + Utils.monitorFile(screenPath, async (f) => { + const v = await Utils.readFileAsync(f); + this.#screen = Number(v) / this.#screenMax; + this.changed("screen"); + }); + + Utils.monitorFile(kbdPath, async (f) => { + const v = await Utils.readFileAsync(f); + this.#kbd = Number(v) / this.#kbdMax; + this.changed("kbd"); + }); + } +} + +export default new Brightness(); diff --git a/overlays/asztal/service/colorpicker.ts b/overlays/asztal/service/colorpicker.ts new file mode 100644 index 0000000..5918f31 --- /dev/null +++ b/overlays/asztal/service/colorpicker.ts @@ -0,0 +1,56 @@ +import icons from "lib/icons" +import { bash, dependencies } from "lib/utils" + +const COLORS_CACHE = Utils.CACHE_DIR + "/colorpicker.json" +const MAX_NUM_COLORS = 10 + +class ColorPicker extends Service { + static { + Service.register(this, {}, { + "colors": ["jsobject"], + }) + } + + #notifID = 0 + #colors = JSON.parse(Utils.readFile(COLORS_CACHE) || "[]") as string[] + + get colors() { return [...this.#colors] } + set colors(colors) { + this.#colors = colors + this.changed("colors") + } + + // TODO: doesn't work? + async wlCopy(color: string) { + if (dependencies("wl-copy")) + bash(`wl-copy ${color}`) + } + + readonly pick = async () => { + if (!dependencies("hyprpicker")) + return + + const color = await bash("hyprpicker -a -r") + if (!color) + return + + this.wlCopy(color) + const list = this.colors + if (!list.includes(color)) { + list.push(color) + if (list.length > MAX_NUM_COLORS) + list.shift() + + this.colors = list + Utils.writeFile(JSON.stringify(list, null, 2), COLORS_CACHE) + } + + this.#notifID = await Utils.notify({ + id: this.#notifID, + iconName: icons.ui.colorpicker, + summary: color, + }) + } +} + +export default new ColorPicker diff --git a/overlays/asztal/service/nix.ts b/overlays/asztal/service/nix.ts new file mode 100644 index 0000000..3bde9fc --- /dev/null +++ b/overlays/asztal/service/nix.ts @@ -0,0 +1,109 @@ +import icons from "lib/icons" +import { bash, dependencies } from "lib/utils" +import options from "options" + +const CACHE = `${Utils.CACHE_DIR}/nixpkgs` +const PREFIX = "legacyPackages.x86_64-linux." +const MAX = options.launcher.nix.max +const nixpkgs = options.launcher.nix.pkgs + +export type Nixpkg = { + name: string + description: string + pname: string + version: string +} + +class Nix extends Service { + static { + Service.register(this, {}, { + "available": ["boolean", "r"], + "ready": ["boolean", "rw"], + }) + } + + #db: { [name: string]: Nixpkg } = {} + #ready = true + + private set ready(r: boolean) { + this.#ready = r + this.changed("ready") + } + + get db() { return this.#db } + get ready() { return this.#ready } + get available() { return Utils.exec("which nix") } + + constructor() { + super() + if (!this.available) + return this + + this.#updateList() + nixpkgs.connect("changed", this.#updateList) + } + + query = async (filter: string) => { + if (!dependencies("fzf", "nix") || !this.#ready) + return [] as string[] + + return bash(`cat ${CACHE} | fzf -f ${filter} -e | head -n ${MAX} `) + .then(str => str.split("\n").filter(i => i)) + } + + nix(cmd: string, bin: string, args: string) { + return Utils.execAsync(`nix ${cmd} ${nixpkgs}#${bin} --impure ${args}`) + } + + run = async (input: string) => { + if (!dependencies("nix")) + return + + try { + const [bin, ...args] = input.trim().split(/\s+/) + + this.ready = false + await this.nix("shell", bin, "--command sh -c 'exit'") + this.ready = true + + this.nix("run", bin, ["--", ...args].join(" ")) + } catch (err) { + if (typeof err === "string") + Utils.notify("NixRun Error", err, icons.nix.nix) + else + logError(err) + } finally { + this.ready = true + } + } + + #updateList = async () => { + if (!dependencies("nix")) + return + + this.ready = false + this.#db = {} + + // const search = await bash(`nix search ${nixpkgs} --json`) + const search = "" + if (!search) { + this.ready = true + return + } + + const json = Object.entries(JSON.parse(search) as { + [name: string]: Nixpkg + }) + + for (const [pkg, info] of json) { + const name = pkg.replace(PREFIX, "") + this.#db[name] = { ...info, name } + } + + const list = Object.keys(this.#db).join("\n") + await Utils.writeFile(list, CACHE) + this.ready = true + } +} + +export default new Nix diff --git a/overlays/asztal/service/powermenu.ts b/overlays/asztal/service/powermenu.ts new file mode 100644 index 0000000..fd16bc1 --- /dev/null +++ b/overlays/asztal/service/powermenu.ts @@ -0,0 +1,43 @@ +import options from "options" + +const { sleep, reboot, logout, shutdown } = options.powermenu + +export type Action = "sleep" | "reboot" | "logout" | "shutdown" + +class PowerMenu extends Service { + static { + Service.register(this, {}, { + "title": ["string"], + "cmd": ["string"], + }) + } + + #title = "" + #cmd = "" + + get title() { return this.#title } + get cmd() { return this.#cmd } + + action(action: Action) { + [this.#cmd, this.#title] = { + sleep: [sleep.value, "Sleep"], + reboot: [reboot.value, "Reboot"], + logout: [logout.value, "Log Out"], + shutdown: [shutdown.value, "Shutdown"], + }[action] + + this.notify("cmd") + this.notify("title") + this.emit("changed") + App.closeWindow("powermenu") + App.openWindow("verification") + } + + readonly shutdown = () => { + this.action("shutdown") + } +} + +const powermenu = new PowerMenu +Object.assign(globalThis, { powermenu }) +export default powermenu diff --git a/overlays/asztal/service/screenrecord.ts b/overlays/asztal/service/screenrecord.ts new file mode 100644 index 0000000..58721d2 --- /dev/null +++ b/overlays/asztal/service/screenrecord.ts @@ -0,0 +1,102 @@ +import GLib from "gi://GLib" +import icons from "lib/icons" +import { dependencies, sh, bash } from "lib/utils" + +const now = () => GLib.DateTime.new_now_local().format("%Y-%m-%d_%H-%M-%S") + +class Recorder extends Service { + static { + Service.register(this, {}, { + "timer": ["int"], + "recording": ["boolean"], + }) + } + + #recordings = Utils.HOME + "/Videos/Screencasting" + #screenshots = Utils.HOME + "/Pictures/Screenshots" + #file = "" + #interval = 0 + + recording = false + timer = 0 + + async start() { + if (!dependencies("slurp", "wf-recorder")) + return + + if (this.recording) + return + + Utils.ensureDirectory(this.#recordings) + this.#file = `${this.#recordings}/${now()}.mp4` + sh(`wf-recorder -g "${await sh("slurp")}" -f ${this.#file} --pixel-format yuv420p`) + + this.recording = true + this.changed("recording") + + this.timer = 0 + this.#interval = Utils.interval(1000, () => { + this.changed("timer") + this.timer++ + }) + } + + async stop() { + if (!this.recording) + return + + await bash("killall -INT wf-recorder") + this.recording = false + this.changed("recording") + GLib.source_remove(this.#interval) + + Utils.notify({ + iconName: icons.fallback.video, + summary: "Screenrecord", + body: this.#file, + actions: { + "Show in Files": () => sh(`xdg-open ${this.#recordings}`), + "View": () => sh(`xdg-open ${this.#file}`), + }, + }) + } + + async screenshot(full = false) { + if (!dependencies("slurp", "wayshot")) + return + + const file = `${this.#screenshots}/${now()}.png` + Utils.ensureDirectory(this.#screenshots) + + if (full) { + await sh(`wayshot -f ${file}`) + } + else { + const size = await sh("slurp") + if (!size) + return + + await sh(`wayshot -f ${file} -s "${size}"`) + } + + bash(`wl-copy < ${file}`) + + Utils.notify({ + image: file, + summary: "Screenshot", + body: file, + actions: { + "Show in Files": () => sh(`xdg-open ${this.#screenshots}`), + "View": () => sh(`xdg-open ${file}`), + "Edit": () => { + if (dependencies("swappy")) + sh(`swappy -f ${file}`) + }, + }, + }) + } +} + +const recorder = new Recorder +Object.assign(globalThis, { recorder }) +export default recorder diff --git a/overlays/asztal/service/wallpaper.ts b/overlays/asztal/service/wallpaper.ts new file mode 100644 index 0000000..0e4bdda --- /dev/null +++ b/overlays/asztal/service/wallpaper.ts @@ -0,0 +1,98 @@ +import options from "options" +import { dependencies, sh } from "lib/utils" + +export type Resolution = 1920 | 1366 | 3840 +export type Market = + | "random" + | "en-US" + | "ja-JP" + | "en-AU" + | "en-GB" + | "de-DE" + | "en-NZ" + | "en-CA" + +const WP = `${Utils.HOME}/.config/background` +const Cache = `${Utils.HOME}/Pictures/Wallpapers/Bing` + +class Wallpaper extends Service { + static { + Service.register(this, {}, { + "wallpaper": ["string"], + }) + } + + #blockMonitor = false + + #wallpaper() { + if (!dependencies("swww")) + return + + sh("hyprctl cursorpos").then(pos => { + sh([ + "swww", "img", + "--transition-type", "grow", + "--transition-pos", pos.replace(" ", ""), + WP, + ]).then(() => { + this.changed("wallpaper") + }) + }) + } + + async #setWallpaper(path: string) { + this.#blockMonitor = true + + await sh(`cp ${path} ${WP}`) + this.#wallpaper() + + this.#blockMonitor = false + } + + async #fetchBing() { + const res = await Utils.fetch("https://bing.biturl.top/", { + params: { + resolution: options.wallpaper.resolution.value, + format: "json", + image_format: "jpg", + index: "random", + mkt: options.wallpaper.market.value, + }, + }).then(res => res.text()) + + if (!res.startsWith("{")) + return console.warn("bing api", res) + + const { url } = JSON.parse(res) + const file = `${Cache}/${url.replace("https://www.bing.com/th?id=", "")}` + + if (dependencies("curl")) { + Utils.ensureDirectory(Cache) + await sh(`curl "${url}" --output ${file}`) + this.#setWallpaper(file) + } + } + + readonly random = () => { this.#fetchBing() } + readonly set = (path: string) => { this.#setWallpaper(path) } + get wallpaper() { return WP } + + constructor() { + super() + + if (!dependencies("swww")) + return this + + // gtk portal + Utils.monitorFile(WP, () => { + if (!this.#blockMonitor) + this.#wallpaper() + }) + + Utils.execAsync("swww-daemon") + .then(this.#wallpaper) + .catch(() => null) + } +} + +export default new Wallpaper diff --git a/overlays/asztal/service/weather.ts b/overlays/asztal/service/weather.ts new file mode 100644 index 0000000..14f2df2 --- /dev/null +++ b/overlays/asztal/service/weather.ts @@ -0,0 +1,59 @@ +import options from "options" + +const { interval, key, cities, unit } = options.datemenu.weather + +class Weather extends Service { + static { + Service.register(this, {}, { + "forecasts": ["jsobject"], + }) + } + + #forecasts: Forecast[] = [] + get forecasts() { return this.#forecasts } + + async #fetch(placeid: number) { + const url = "https://api.openweathermap.org/data/2.5/forecast" + const res = await Utils.fetch(url, { + params: { + id: placeid, + appid: key.value, + untis: unit.value, + }, + }) + return await res.json() + } + + constructor() { + super() + if (!key.value) + return this + + Utils.interval(interval.value, () => { + Promise.all(cities.value.map(this.#fetch)).then(forecasts => { + this.#forecasts = forecasts as Forecast[] + this.changed("forecasts") + }) + }) + } +} + +export default new Weather + +type Forecast = { + city: { + name: string, + } + list: Array<{ + dt: number + main: { + temp: number + feels_like: number + }, + weather: Array<{ + main: string, + description: string, + icon: string, + }> + }> +} diff --git a/overlays/asztal/style/extra.scss b/overlays/asztal/style/extra.scss new file mode 100644 index 0000000..e7f9d44 --- /dev/null +++ b/overlays/asztal/style/extra.scss @@ -0,0 +1,67 @@ +@import './mixins/button.scss'; + +* { + font-size: $font-size; + font-family: $font-name; +} + +separator { + &.horizontal { + min-height: $border-width; + } + + &.vertical { + min-width: $border-width; + } +} + +window.popup { + >* { + border: none; + box-shadow: none; + } + + menu { + border-radius: $popover-radius; + background-color: $bg; + padding: $popover-padding; + border: $border-width solid $popover-border-color; + + separator { + background-color: $border-color; + } + + menuitem { + @include button; + padding: $spacing * .5; + margin: ($spacing * .5) 0; + + &:first-child { + margin-top: 0; + } + + &:last-child { + margin-bottom: 0; + } + } + } +} + +tooltip { + * { + all: unset; + } + + background-color: transparent; + border: none; + + >*>* { + background-color: $bg; + border-radius: $radius; + border: $border-width solid $popover-border-color; + color: $fg; + padding: 8px; + margin: 4px; + box-shadow: 0 0 3px 0 $shadow-color; + } +} diff --git a/overlays/asztal/style/mixins/a11y-button.scss b/overlays/asztal/style/mixins/a11y-button.scss new file mode 100644 index 0000000..00b24c6 --- /dev/null +++ b/overlays/asztal/style/mixins/a11y-button.scss @@ -0,0 +1,48 @@ +@import './button'; + +@mixin accs-button($flat: false, $reactive: true) { + @include unset; + color: $fg; + + >* { + border-radius: $radius; + transition: $transition; + + @if $flat { + background-color: transparent; + box-shadow: none; + } + + @else { + background-color: $widget-bg; + box-shadow: inset 0 0 0 $border-width $border-color; + } + } + + + @if $reactive { + + &:focus>*, + &.focused>* { + @include button-focus; + } + + &:hover>* { + @include button-hover; + } + + &:active, + &.active, + &.on, + &:checked { + >* { + @include button-active; + } + + &:hover>* { + box-shadow: inset 0 0 0 $border-width $border-color, + inset 0 0 0 99px $hover-bg; + } + } + } +} diff --git a/overlays/asztal/style/mixins/button.scss b/overlays/asztal/style/mixins/button.scss new file mode 100644 index 0000000..79ec275 --- /dev/null +++ b/overlays/asztal/style/mixins/button.scss @@ -0,0 +1,70 @@ +@mixin button-focus() { + box-shadow: inset 0 0 0 $border-width $primary-bg; + background-color: $hover-bg; + color: $hover-fg; +} + +@mixin button-hover() { + box-shadow: inset 0 0 0 $border-width $border-color; + background-color: $hover-bg; + color: $hover-fg; +} + +@mixin button-active() { + box-shadow: inset 0 0 0 $border-width $border-color; + background-image: $active-gradient; + background-color: $primary-bg; + color: $primary-fg; +} + +@mixin button-disabled() { + box-shadow: none; + background-color: transparent; + color: transparentize($fg, 0.7); +} + +@mixin button($flat: false, $reactive: true, $radius: $radius, $focusable: true) { + all: unset; + transition: $transition; + border-radius: $radius; + color: $fg; + + @if $flat { + background-color: transparent; + background-image: none; + box-shadow: none; + } + + @else { + background-color: $widget-bg; + box-shadow: inset 0 0 0 $border-width $border-color; + } + + @if $reactive { + @if $focusable { + &:focus { + @include button-focus; + } + } + + &:hover { + @include button-hover; + } + + &:active, + &.on, + &.active, + &:checked { + @include button-active; + + &:hover { + box-shadow: inset 0 0 0 $border-width $border-color, + inset 0 0 0 99px $hover-bg; + } + } + } + + &:disabled { + @include button-disabled; + } +} diff --git a/overlays/asztal/style/mixins/floating-widget.scss b/overlays/asztal/style/mixins/floating-widget.scss new file mode 100644 index 0000000..613668d --- /dev/null +++ b/overlays/asztal/style/mixins/floating-widget.scss @@ -0,0 +1,12 @@ +@mixin floating-widget { + @if $shadows { + box-shadow: 0 0 5px 0 $shadow-color; + } + + margin: max($spacing, 8px); + border: $border-width solid $popover-border-color; + background-color: $bg; + color: $fg; + border-radius: $popover-radius; + padding: $popover-padding; +} diff --git a/overlays/asztal/style/mixins/hidden.scss b/overlays/asztal/style/mixins/hidden.scss new file mode 100644 index 0000000..ea6a42c --- /dev/null +++ b/overlays/asztal/style/mixins/hidden.scss @@ -0,0 +1,15 @@ +@mixin hidden { + background-color: transparent; + background-image: none; + border-color: transparent; + box-shadow: none; + -gtk-icon-transform: scale(0); + + * { + background-color: transparent; + background-image: none; + border-color: transparent; + box-shadow: none; + -gtk-icon-transform: scale(0); + } +} diff --git a/overlays/asztal/style/mixins/media.scss b/overlays/asztal/style/mixins/media.scss new file mode 100644 index 0000000..3178029 --- /dev/null +++ b/overlays/asztal/style/mixins/media.scss @@ -0,0 +1,42 @@ +@mixin media() { + @include widget; + padding: $padding; + + .cover { + @if $shadows { + box-shadow: 2px 2px 2px 0 $shadow-color; + } + + background-size: cover; + background-position: center; + border-radius: $radius*0.8; + margin-right: $spacing; + } + + button { + @include button($flat: true); + padding: $padding * .5; + + &.play-pause { + margin: 0 ($spacing * .5); + } + + image { + font-size: 1.2em; + } + } + + .artist { + color: transparentize($fg, .2); + font-size: .9em; + } + + scale { + @include slider($width: .5em, $slider: false, $gradient: linear-gradient($fg, $fg)); + margin-bottom: $padding * .5; + + trough { + border: none; + } + } +} diff --git a/overlays/asztal/style/mixins/scrollable.scss b/overlays/asztal/style/mixins/scrollable.scss new file mode 100644 index 0000000..b66f246 --- /dev/null +++ b/overlays/asztal/style/mixins/scrollable.scss @@ -0,0 +1,42 @@ +@mixin scrollable($top: false, $bottom: false) { + + @if $top and $shadows { + undershoot.top { + background: linear-gradient(to bottom, $shadow-color, transparent, transparent, transparent, transparent, transparent); + } + } + + @if $bottom and $shadows { + undershoot.bottom { + background: linear-gradient(to top, $shadow-color, transparent, transparent, transparent, transparent, transparent); + } + } + + scrollbar, + scrollbar * { + all: unset; + } + + scrollbar.vertical { + transition: $transition; + background-color: transparentize($bg, 0.7); + + &:hover { + background-color: transparentize($bg, 0.3); + + slider { + background-color: transparentize($fg, 0.3); + min-width: .6em; + } + } + } + + + scrollbar.vertical slider { + background-color: transparentize($fg, 0.5); + border-radius: $radius; + min-width: .4em; + min-height: 2em; + transition: $transition; + } +} diff --git a/overlays/asztal/style/mixins/slider.scss b/overlays/asztal/style/mixins/slider.scss new file mode 100644 index 0000000..b90e566 --- /dev/null +++ b/overlays/asztal/style/mixins/slider.scss @@ -0,0 +1,74 @@ +@import './unset'; + +@mixin slider($width: 0.7em, $slider-width: .5em, $gradient: $active-gradient, $slider: true, $focusable: true, $radius: $radius) { + @include unset($rec: true); + + trough { + transition: $transition; + border-radius: $radius; + border: $border; + background-color: $widget-bg; + min-height: $width; + min-width: $width; + + highlight, + progress { + border-radius: max($radius - $border-width, 0); + background-image: $gradient; + min-height: $width; + min-width: $width; + } + } + + slider { + box-shadow: none; + background-color: transparent; + border: $border-width solid transparent; + transition: $transition; + border-radius: $radius; + min-height: $width; + min-width: $width; + margin: -$slider-width; + } + + &:hover { + trough { + background-color: $hover-bg; + } + + slider { + @if $slider { + background-color: $fg; + border-color: $border-color; + + @if $shadows { + box-shadow: 0 0 3px 0 $shadow-color; + } + } + } + } + + &:disabled { + + highlight, + progress { + background-color: transparentize($fg, 0.4); + background-image: none; + } + } + + @if $focusable { + trough:focus { + background-color: $hover-bg; + box-shadow: inset 0 0 0 $border-width $primary-bg; + + slider { + @if $slider { + background-color: $fg; + box-shadow: inset 0 0 0 $border-width $primary-bg; + } + } + } + + } +} diff --git a/overlays/asztal/style/mixins/spacing.scss b/overlays/asztal/style/mixins/spacing.scss new file mode 100644 index 0000000..4096fba --- /dev/null +++ b/overlays/asztal/style/mixins/spacing.scss @@ -0,0 +1,53 @@ +@mixin spacing($multiplier: 1, $spacing: $spacing, $rec: false) { + &.horizontal>* { + margin: 0 calc($spacing * $multiplier / 2); + + &:first-child { + margin-left: 0; + } + + &:last-child { + margin-right: 0; + } + } + + &.vertical>* { + margin: calc($spacing * $multiplier / 2) 0; + + &:first-child { + margin-top: 0; + } + + &:last-child { + margin-bottom: 0; + } + } + + @if $rec { + box { + &.horizontal>* { + margin: 0 $spacing * $multiplier / 2; + + &:first-child { + margin-left: 0; + } + + &:last-child { + margin-right: 0; + } + } + + &.vertical>* { + margin: $spacing * $multiplier / 2 0; + + &:first-child { + margin-top: 0; + } + + &:last-child { + margin-bottom: 0; + } + } + } + } +} diff --git a/overlays/asztal/style/mixins/switch.scss b/overlays/asztal/style/mixins/switch.scss new file mode 100644 index 0000000..2abf360 --- /dev/null +++ b/overlays/asztal/style/mixins/switch.scss @@ -0,0 +1,16 @@ +@import './button'; + +@mixin switch { + @include button; + + slider { + background-color: $primary-fg; + border-radius: $radius; + min-width: 24px; + min-height: 24px; + } + + image { + color: transparent; + } +} diff --git a/overlays/asztal/style/mixins/unset.scss b/overlays/asztal/style/mixins/unset.scss new file mode 100644 index 0000000..eb80af5 --- /dev/null +++ b/overlays/asztal/style/mixins/unset.scss @@ -0,0 +1,9 @@ +@mixin unset($rec: false) { + all: unset; + + @if $rec { + * { + all: unset + } + } +} diff --git a/overlays/asztal/style/mixins/widget.scss b/overlays/asztal/style/mixins/widget.scss new file mode 100644 index 0000000..053f1aa --- /dev/null +++ b/overlays/asztal/style/mixins/widget.scss @@ -0,0 +1,7 @@ +@mixin widget { + transition: $transition; + border-radius: $radius; + color: $fg; + background-color: $widget-bg; + border: $border; +} diff --git a/overlays/asztal/style/style.ts b/overlays/asztal/style/style.ts new file mode 100644 index 0000000..a9b94fe --- /dev/null +++ b/overlays/asztal/style/style.ts @@ -0,0 +1,103 @@ +/* eslint-disable max-len */ +import { type Opt } from "lib/option" +import options from "options" +import { bash, dependencies, sh } from "lib/utils" + +const deps = [ + "font", + "theme", + "bar.flatButtons", + "bar.position", + "bar.battery.charging", + "bar.battery.blocks", +] + +const { + dark, + light, + blur, + scheme, + padding, + spacing, + radius, + shadows, + widget, + border, +} = options.theme + +const popoverPaddingMultiplier = 1.6 + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const t = (dark: Opt | string, light: Opt | string) => scheme.value === "dark" + ? `${dark}` : `${light}` + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const $ = (name: string, value: string | Opt) => `$${name}: ${value};` + +const variables = () => [ + $("bg", blur.value ? `transparentize(${t(dark.bg, light.bg)}, ${blur.value / 100})` : t(dark.bg, light.bg)), + $("fg", t(dark.fg, light.fg)), + + $("primary-bg", t(dark.primary.bg, light.primary.bg)), + $("primary-fg", t(dark.primary.fg, light.primary.fg)), + + $("error-bg", t(dark.error.bg, light.error.bg)), + $("error-fg", t(dark.error.fg, light.error.fg)), + + $("scheme", scheme), + $("padding", `${padding}pt`), + $("spacing", `${spacing}pt`), + $("radius", `${radius}px`), + $("transition", `${options.transition}ms`), + + $("shadows", `${shadows}`), + + $("widget-bg", `transparentize(${t(dark.widget, light.widget)}, ${widget.opacity.value / 100})`), + + $("hover-bg", `transparentize(${t(dark.widget, light.widget)}, ${(widget.opacity.value * .9) / 100})`), + $("hover-fg", `lighten(${t(dark.fg, light.fg)}, 8%)`), + + $("border-width", `${border.width}px`), + $("border-color", `transparentize(${t(dark.border, light.border)}, ${border.opacity.value / 100})`), + $("border", "$border-width solid $border-color"), + + $("active-gradient", `linear-gradient(to right, ${t(dark.primary.bg, light.primary.bg)}, darken(${t(dark.primary.bg, light.primary.bg)}, 4%))`), + $("shadow-color", t("rgba(0,0,0,.6)", "rgba(0,0,0,.4)")), + $("text-shadow", t("2pt 2pt 2pt $shadow-color", "none")), + + $("popover-border-color", `transparentize(${t(dark.border, light.border)}, ${Math.max(((border.opacity.value - 1) / 100), 0)})`), + $("popover-padding", `$padding * ${popoverPaddingMultiplier}`), + $("popover-radius", radius.value === 0 ? "0" : "$radius + $popover-padding"), + + $("font-size", `${options.font.size}pt`), + $("font-name", options.font.name), + + // etc + $("charging-bg", options.bar.battery.charging), + $("bar-battery-blocks", options.bar.battery.blocks), + $("bar-position", options.bar.position), + $("hyprland-gaps-multiplier", options.hyprland.gaps), +] + +async function resetCss() { + if (!dependencies("sass", "fd")) + return + + try { + const vars = `${TMP}/variables.scss` + await Utils.writeFile(variables().join("\n"), vars) + + const fd = await sh(`fd ".scss" ${App.configDir}`) + const files = fd.split(/\s+/).map(f => `@import '${f}';`) + const scss = [`@import '${vars}';`, ...files].join("\n") + const css = await bash`echo "${scss}" | sass --stdin` + + App.applyCss(css, true) + } catch (error) { + logError(error) + } +} + +Utils.monitorFile(App.configDir, resetCss) +options.handler(deps, resetCss) +await resetCss() diff --git a/overlays/asztal/tsconfig.json b/overlays/asztal/tsconfig.json new file mode 100644 index 0000000..1708aa3 --- /dev/null +++ b/overlays/asztal/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ES2022", + "lib": [ + "ES2022" + ], + "allowJs": true, + "checkJs": true, + "strict": true, + "noImplicitAny": false, + "baseUrl": ".", + "typeRoots": [ + "./types", + "./node_modules/@girs" + ], + "skipLibCheck": true + } +} diff --git a/overlays/asztal/widget/PopupWindow.ts b/overlays/asztal/widget/PopupWindow.ts new file mode 100644 index 0000000..b53b6fd --- /dev/null +++ b/overlays/asztal/widget/PopupWindow.ts @@ -0,0 +1,156 @@ +import { type WindowProps } from "types/widgets/window" +import { type RevealerProps } from "types/widgets/revealer" +import { type EventBoxProps } from "types/widgets/eventbox" +import type Gtk from "gi://Gtk?version=3.0" +import options from "options" + +type Transition = RevealerProps["transition"] +type Child = WindowProps["child"] + +type PopupWindowProps = Omit & { + name: string + layout?: keyof ReturnType + transition?: Transition, +} + +export const Padding = (name: string, { + css = "", + hexpand = true, + vexpand = true, +}: EventBoxProps = {}) => Widget.EventBox({ + hexpand, + vexpand, + can_focus: false, + child: Widget.Box({ css }), + setup: w => w.on("button-press-event", () => App.toggleWindow(name)), +}) + +const PopupRevealer = ( + name: string, + child: Child, + transition: Transition = "slide_down", +) => Widget.Box( + { css: "padding: 1px;" }, + Widget.Revealer({ + transition, + child: Widget.Box({ + class_name: "window-content", + child, + }), + transitionDuration: options.transition.bind(), + setup: self => self.hook(App, (_, wname, visible) => { + if (wname === name) + self.reveal_child = visible + }), + }), +) + +const Layout = (name: string, child: Child, transition?: Transition) => ({ + "center": () => Widget.CenterBox({}, + Padding(name), + Widget.CenterBox( + { vertical: true }, + Padding(name), + PopupRevealer(name, child, transition), + Padding(name), + ), + Padding(name), + ), + "top": () => Widget.CenterBox({}, + Padding(name), + Widget.Box( + { vertical: true }, + PopupRevealer(name, child, transition), + Padding(name), + ), + Padding(name), + ), + "top-right": () => Widget.Box({}, + Padding(name), + Widget.Box( + { + hexpand: false, + vertical: true, + }, + PopupRevealer(name, child, transition), + Padding(name), + ), + ), + "top-center": () => Widget.Box({}, + Padding(name), + Widget.Box( + { + hexpand: false, + vertical: true, + }, + PopupRevealer(name, child, transition), + Padding(name), + ), + Padding(name), + ), + "top-left": () => Widget.Box({}, + Widget.Box( + { + hexpand: false, + vertical: true, + }, + PopupRevealer(name, child, transition), + Padding(name), + ), + Padding(name), + ), + "bottom-left": () => Widget.Box({}, + Widget.Box( + { + hexpand: false, + vertical: true, + }, + Padding(name), + PopupRevealer(name, child, transition), + ), + Padding(name), + ), + "bottom-center": () => Widget.Box({}, + Padding(name), + Widget.Box( + { + hexpand: false, + vertical: true, + }, + Padding(name), + PopupRevealer(name, child, transition), + ), + Padding(name), + ), + "bottom-right": () => Widget.Box({}, + Padding(name), + Widget.Box( + { + hexpand: false, + vertical: true, + }, + Padding(name), + PopupRevealer(name, child, transition), + ), + ), +}) + +export default ({ + name, + child, + layout = "center", + transition, + exclusivity = "ignore", + ...props +}: PopupWindowProps) => 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", "bottom", "right", "left"], + child: Layout(name, child, transition)[layout](), + ...props, +}) diff --git a/overlays/asztal/widget/RegularWindow.ts b/overlays/asztal/widget/RegularWindow.ts new file mode 100644 index 0000000..1e4225d --- /dev/null +++ b/overlays/asztal/widget/RegularWindow.ts @@ -0,0 +1,3 @@ +import Gtk from "gi://Gtk?version=3.0" + +export default Widget.subclass(Gtk.Window) diff --git a/overlays/asztal/widget/bar/Bar.ts b/overlays/asztal/widget/bar/Bar.ts new file mode 100644 index 0000000..9450103 --- /dev/null +++ b/overlays/asztal/widget/bar/Bar.ts @@ -0,0 +1,57 @@ +import BatteryBar from "./buttons/BatteryBar" +import ColorPicker from "./buttons/ColorPicker" +import Date from "./buttons/Date" +import Launcher from "./buttons/Launcher" +import Media from "./buttons/Media" +import PowerMenu from "./buttons/PowerMenu" +import SysTray from "./buttons/SysTray" +import SystemIndicators from "./buttons/SystemIndicators" +import Taskbar from "./buttons/Taskbar" +import Workspaces from "./buttons/Workspaces" +import ScreenRecord from "./buttons/ScreenRecord" +import Messages from "./buttons/Messages" +import options from "options" + +const { start, center, end } = options.bar.layout +const pos = options.bar.position.bind() + +export type BarWidget = keyof typeof widget + +const widget = { + battery: BatteryBar, + colorpicker: ColorPicker, + date: Date, + launcher: Launcher, + media: Media, + powermenu: PowerMenu, + systray: SysTray, + system: SystemIndicators, + taskbar: Taskbar, + workspaces: Workspaces, + screenrecord: ScreenRecord, + messages: Messages, + expander: () => Widget.Box({ expand: true }), +} + +export default (monitor: number) => Widget.Window({ + monitor, + class_name: "bar", + name: `bar${monitor}`, + exclusivity: "exclusive", + anchor: pos.as(pos => [pos, "right", "left"]), + child: Widget.CenterBox({ + css: "min-width: 2px; min-height: 2px;", + startWidget: Widget.Box({ + hexpand: true, + children: start.bind().as(s => s.map(w => widget[w]())), + }), + centerWidget: Widget.Box({ + hpack: "center", + children: center.bind().as(c => c.map(w => widget[w]())), + }), + endWidget: Widget.Box({ + hexpand: true, + children: end.bind().as(e => e.map(w => widget[w]())), + }), + }), +}) diff --git a/overlays/asztal/widget/bar/PanelButton.ts b/overlays/asztal/widget/bar/PanelButton.ts new file mode 100644 index 0000000..1e5fafc --- /dev/null +++ b/overlays/asztal/widget/bar/PanelButton.ts @@ -0,0 +1,46 @@ +import options from "options" +import { ButtonProps } from "types/widgets/button" + +type PanelButtonProps = ButtonProps & { + window?: string, + flat?: boolean +} + +export default ({ + window = "", + flat, + child, + setup, + ...rest +}: PanelButtonProps) => Widget.Button({ + child: Widget.Box({ child }), + setup: self => { + let open = false + + self.toggleClassName("panel-button") + self.toggleClassName(window) + + self.hook(options.bar.flatButtons, () => { + self.toggleClassName("flat", flat ?? options.bar.flatButtons.value) + }) + + self.hook(App, (_, win, visible) => { + if (win !== window) + return + + if (open && !visible) { + open = false + self.toggleClassName("active", false) + } + + if (visible) { + open = true + self.toggleClassName("active") + } + }) + + if (setup) + setup(self) + }, + ...rest, +}) diff --git a/overlays/asztal/widget/bar/ScreenCorners.ts b/overlays/asztal/widget/bar/ScreenCorners.ts new file mode 100644 index 0000000..1b35e50 --- /dev/null +++ b/overlays/asztal/widget/bar/ScreenCorners.ts @@ -0,0 +1,25 @@ +import options from "options" + +const { corners } = options.bar + +export default (monitor: number) => Widget.Window({ + monitor, + name: `corner${monitor}`, + class_name: "screen-corner", + anchor: ["top", "bottom", "right", "left"], + click_through: true, + child: Widget.Box({ + class_name: "shadow", + child: Widget.Box({ + class_name: "border", + expand: true, + child: Widget.Box({ + class_name: "corner", + expand: true, + }), + }), + }), + setup: self => self.hook(corners, () => { + self.toggleClassName("corners", corners.value) + }), +}) diff --git a/overlays/asztal/widget/bar/bar.scss b/overlays/asztal/widget/bar/bar.scss new file mode 100644 index 0000000..472894c --- /dev/null +++ b/overlays/asztal/widget/bar/bar.scss @@ -0,0 +1,234 @@ +@use 'sass:color'; + +$bar-spacing: $spacing * .3; +$button-radius: $radius; + +@mixin panel-button($flat: true, $reactive: true) { + @include accs-button($flat, $reactive); + + >* { + border-radius: $button-radius; + margin: $bar-spacing; + } + + label, + image { + font-weight: bold; + } + + >* { + padding: $padding * 0.4 $padding * 0.8; + } +} + +.bar { + background-color: $bg; + + .panel-button { + @include panel-button; + + &:not(.flat) { + + @include accs-button($flat: false); + } + } + + .launcher { + .colored { + color: transparentize($primary-bg, 0.2); + } + + &:hover .colored { + color: $primary-bg; + } + + &:active .colored, + &.active .colored { + color: $primary-fg; + } + } + + .workspaces { + label { + font-size: 0; + min-width: 5pt; + min-height: 5pt; + border-radius: $radius*.6; + box-shadow: inset 0 0 0 $border-width $border-color; + margin: 0 $padding * .5; + transition: $transition* .5; + background-color: transparentize($fg, .8); + + &.occupied { + background-color: transparentize($fg, .2); + min-width: 7pt; + min-height: 7pt; + } + + &.active { + // background-color: $primary-bg; + background-image: $active-gradient; + min-width: 20pt; + min-height: 12pt; + } + } + + &.active, + &:active { + label { + background-color: transparentize($primary-fg, .3); + + &.occupied { + background-color: transparentize($primary-fg, .15); + } + + &.active { + background-color: $primary-fg; + } + } + } + } + + .media label { + margin: 0 ($spacing * .5) + } + + .taskbar .indicator.active { + background-color: $primary-bg; + border-radius: $radius; + min-height: 4pt; + min-width: 6pt; + margin: 2pt; + } + + .powermenu.colored, + .recorder { + image { + color: transparentize($error-bg, 0.3); + } + + &:hover image { + color: transparentize($error-bg, 0.15); + } + + &:active image { + color: $primary-fg; + } + } + + .quicksettings>box>box { + @include spacing($spacing: if($bar-spacing==0, $padding / 2, $bar-spacing)); + } + + .quicksettings:not(.active):not(:active) { + .bluetooth { + color: $primary-bg; + + label { + font-size: $font-size * .7; + color: $fg; + text-shadow: $text-shadow; + } + } + } + + .battery-bar { + >* { + padding: 0; + } + + &.bar-hidden>box { + padding: 0 $spacing * .5; + + image { + margin: 0; + } + } + + levelbar * { + all: unset; + transition: $transition; + } + + .whole { + @if $shadows { + image { + -gtk-icon-shadow: $text-shadow; + } + + label { + text-shadow: $text-shadow; + } + } + } + + .regular image { + margin-left: $spacing * .5; + } + + trough { + @include widget; + min-height: 12pt; + min-width: 12pt; + } + + .regular trough { + margin-right: $spacing * .5; + } + + block { + margin: 0; + + &:last-child { + border-radius: 0 $button-radius $button-radius 0; + } + + &:first-child { + border-radius: $button-radius 0 0 $button-radius; + } + } + + .vertical { + block { + &:last-child { + border-radius: 0 0 $button-radius $button-radius; + } + + &:first-child { + border-radius: $button-radius $button-radius 0 0; + } + } + + } + + @for $i from 1 through $bar-battery-blocks { + block:nth-child(#{$i}).filled { + background-color: color.mix($bg, $primary-bg, $i*3) + } + + &.low block:nth-child(#{$i}).filled { + background-color: color.mix($bg, $error-bg, $i*3) + } + + &.charging block:nth-child(#{$i}).filled { + background-color: color.mix($bg, $charging-bg, $i*3) + } + + &:active .regular block:nth-child(#{$i}).filled { + background-color: color.mix($bg, $primary-fg, $i*3) + } + } + + &.low image { + color: $error-bg + } + + &.charging image { + color: $charging-bg + } + + &:active image { + color: $primary-fg + } + } +} diff --git a/overlays/asztal/widget/bar/buttons/BatteryBar.ts b/overlays/asztal/widget/bar/buttons/BatteryBar.ts new file mode 100644 index 0000000..18de329 --- /dev/null +++ b/overlays/asztal/widget/bar/buttons/BatteryBar.ts @@ -0,0 +1,94 @@ +import icons from "lib/icons" +import options from "options" +import PanelButton from "../PanelButton" + +const battery = await Service.import("battery") +const { bar, percentage, blocks, width, low } = options.bar.battery + +const Indicator = () => Widget.Icon({ + setup: self => self.hook(battery, () => { + self.icon = battery.charging || battery.charged + ? icons.battery.charging + : battery.icon_name + }), +}) + +const PercentLabel = () => Widget.Revealer({ + transition: "slide_right", + click_through: true, + reveal_child: percentage.bind(), + child: Widget.Label({ + label: battery.bind("percent").as(p => `${p}%`), + }), +}) + +const LevelBar = () => { + const level = Widget.LevelBar({ + bar_mode: "discrete", + max_value: blocks.bind(), + visible: bar.bind().as(b => b !== "hidden"), + value: battery.bind("percent").as(p => (p / 100) * blocks.value), + }) + const update = () => { + level.value = (battery.percent / 100) * blocks.value + level.css = `block { min-width: ${width.value / blocks.value}pt; }` + } + return level + .hook(width, update) + .hook(blocks, update) + .hook(bar, () => { + level.vpack = bar.value === "whole" ? "fill" : "center" + level.hpack = bar.value === "whole" ? "fill" : "center" + }) +} + +const WholeButton = () => Widget.Overlay({ + vexpand: true, + child: LevelBar(), + class_name: "whole", + pass_through: true, + overlay: Widget.Box({ + hpack: "center", + children: [ + Widget.Icon({ + icon: icons.battery.charging, + visible: Utils.merge([ + battery.bind("charging"), + battery.bind("charged"), + ], (ing, ed) => ing || ed), + }), + Widget.Box({ + hpack: "center", + vpack: "center", + child: PercentLabel(), + }), + ], + }), +}) + +const Regular = () => Widget.Box({ + class_name: "regular", + children: [ + Indicator(), + PercentLabel(), + LevelBar(), + ], +}) + +export default () => PanelButton({ + class_name: "battery-bar", + hexpand: false, + on_clicked: () => { percentage.value = !percentage.value }, + visible: battery.bind("available"), + child: Widget.Box({ + expand: true, + visible: battery.bind("available"), + child: bar.bind().as(b => b === "whole" ? WholeButton() : Regular()), + }), + setup: self => self + .hook(bar, w => w.toggleClassName("bar-hidden", bar.value === "hidden")) + .hook(battery, w => { + w.toggleClassName("charging", battery.charging || battery.charged) + w.toggleClassName("low", battery.percent < low.value) + }), +}) diff --git a/overlays/asztal/widget/bar/buttons/ColorPicker.ts b/overlays/asztal/widget/bar/buttons/ColorPicker.ts new file mode 100644 index 0000000..5b1f3f6 --- /dev/null +++ b/overlays/asztal/widget/bar/buttons/ColorPicker.ts @@ -0,0 +1,37 @@ +import PanelButton from "../PanelButton" +import colorpicker from "service/colorpicker" +import Gdk from "gi://Gdk" + +const css = (color: string) => ` +* { + background-color: ${color}; + color: transparent; +} +*:hover { + color: white; + text-shadow: 2px 2px 3px rgba(0,0,0,.8); +}` + +export default () => { + const menu = Widget.Menu({ + class_name: "colorpicker", + children: colorpicker.bind("colors").as(c => c.map(color => Widget.MenuItem({ + child: Widget.Label(color), + css: css(color), + on_activate: () => colorpicker.wlCopy(color), + }))), + }) + + return PanelButton({ + class_name: "color-picker", + child: Widget.Icon("color-select-symbolic"), + tooltip_text: colorpicker.bind("colors").as(v => `${v.length} colors`), + on_clicked: colorpicker.pick, + on_secondary_click: self => { + if (colorpicker.colors.length === 0) + return + + menu.popup_at_widget(self, Gdk.Gravity.SOUTH, Gdk.Gravity.NORTH, null) + }, + }) +} diff --git a/overlays/asztal/widget/bar/buttons/Date.ts b/overlays/asztal/widget/bar/buttons/Date.ts new file mode 100644 index 0000000..4c71afb --- /dev/null +++ b/overlays/asztal/widget/bar/buttons/Date.ts @@ -0,0 +1,15 @@ +import { clock } from "lib/variables" +import PanelButton from "../PanelButton" +import options from "options" + +const { format, action } = options.bar.date +const time = Utils.derive([clock, format], (c, f) => c.format(f) || "") + +export default () => PanelButton({ + window: "datemenu", + on_clicked: action.bind(), + child: Widget.Label({ + justification: "center", + label: time.bind(), + }), +}) diff --git a/overlays/asztal/widget/bar/buttons/Launcher.ts b/overlays/asztal/widget/bar/buttons/Launcher.ts new file mode 100644 index 0000000..f3fee6b --- /dev/null +++ b/overlays/asztal/widget/bar/buttons/Launcher.ts @@ -0,0 +1,49 @@ +import PanelButton from "../PanelButton" +import options from "options" +import nix from "service/nix" + +const { icon, label, action } = options.bar.launcher + +function Spinner() { + const child = Widget.Icon({ + icon: icon.icon.bind(), + class_name: Utils.merge([ + icon.colored.bind(), + nix.bind("ready"), + ], (c, r) => `${c ? "colored" : ""} ${r ? "" : "spinning"}`), + css: ` + @keyframes spin { + to { -gtk-icon-transform: rotate(1turn); } + } + + image.spinning { + animation-name: spin; + animation-duration: 1s; + animation-timing-function: linear; + animation-iteration-count: infinite; + } + `, + }) + + return Widget.Revealer({ + transition: "slide_left", + child, + reveal_child: Utils.merge([ + icon.icon.bind(), + nix.bind("ready"), + ], (i, r) => Boolean(i || r)), + }) +} + +export default () => PanelButton({ + window: "launcher", + on_clicked: action.bind(), + child: Widget.Box([ + Spinner(), + Widget.Label({ + class_name: label.colored.bind().as(c => c ? "colored" : ""), + visible: label.label.bind().as(v => !!v), + label: label.label.bind(), + }), + ]), +}) diff --git a/overlays/asztal/widget/bar/buttons/Media.ts b/overlays/asztal/widget/bar/buttons/Media.ts new file mode 100644 index 0000000..b3aab61 --- /dev/null +++ b/overlays/asztal/widget/bar/buttons/Media.ts @@ -0,0 +1,92 @@ +import { type MprisPlayer } from "types/service/mpris" +import PanelButton from "../PanelButton" +import options from "options" +import icons from "lib/icons" +import { icon } from "lib/utils" + +const mpris = await Service.import("mpris") +const { length, direction, preferred, monochrome, format } = options.bar.media + +const getPlayer = (name = preferred.value) => + mpris.getPlayer(name) || mpris.players[0] || null + +const Content = (player: MprisPlayer) => { + const revealer = Widget.Revealer({ + click_through: true, + visible: length.bind().as(l => l > 0), + transition: direction.bind().as(d => `slide_${d}` as const), + setup: self => { + let current = "" + self.hook(player, () => { + if (current === player.track_title) + return + + current = player.track_title + self.reveal_child = true + Utils.timeout(3000, () => { + !self.is_destroyed && (self.reveal_child = false) + }) + }) + }, + child: Widget.Label({ + truncate: "end", + max_width_chars: length.bind().as(n => n > 0 ? n : -1), + label: Utils.merge([ + player.bind("track_title"), + player.bind("track_artists"), + format.bind(), + ], () => `${format}` + .replace("{title}", player.track_title) + .replace("{artists}", player.track_artists.join(", ")) + .replace("{artist}", player.track_artists[0] || "") + .replace("{album}", player.track_album) + .replace("{name}", player.name) + .replace("{identity}", player.identity), + ), + }), + }) + + const playericon = Widget.Icon({ + icon: Utils.merge([player.bind("entry"), monochrome.bind()], (entry => { + const name = `${entry}${monochrome.value ? "-symbolic" : ""}` + return icon(name, icons.fallback.audio) + })), + }) + + return Widget.Box({ + attribute: { revealer }, + children: direction.bind().as(d => d === "right" + ? [playericon, revealer] : [revealer, playericon]), + }) +} + +export default () => { + let player = getPlayer() + + const btn = PanelButton({ + class_name: "media", + child: Widget.Icon(icons.fallback.audio), + }) + + const update = () => { + player = getPlayer() + btn.visible = !!player + + if (!player) + return + + const content = Content(player) + const { revealer } = content.attribute + btn.child = content + btn.on_primary_click = () => { player.playPause() } + btn.on_secondary_click = () => { player.playPause() } + btn.on_scroll_up = () => { player.next() } + btn.on_scroll_down = () => { player.previous() } + btn.on_hover = () => { revealer.reveal_child = true } + btn.on_hover_lost = () => { revealer.reveal_child = false } + } + + return btn + .hook(preferred, update) + .hook(mpris, update, "notify::players") +} diff --git a/overlays/asztal/widget/bar/buttons/Messages.ts b/overlays/asztal/widget/bar/buttons/Messages.ts new file mode 100644 index 0000000..a8971e9 --- /dev/null +++ b/overlays/asztal/widget/bar/buttons/Messages.ts @@ -0,0 +1,16 @@ +import icons from "lib/icons" +import PanelButton from "../PanelButton" +import options from "options" + +const n = await Service.import("notifications") +const notifs = n.bind("notifications") +const action = options.bar.messages.action.bind() + +export default () => PanelButton({ + class_name: "messages", + on_clicked: action, + visible: notifs.as(n => n.length > 0), + child: Widget.Box([ + Widget.Icon(icons.notifications.message), + ]), +}) diff --git a/overlays/asztal/widget/bar/buttons/PowerMenu.ts b/overlays/asztal/widget/bar/buttons/PowerMenu.ts new file mode 100644 index 0000000..4432ade --- /dev/null +++ b/overlays/asztal/widget/bar/buttons/PowerMenu.ts @@ -0,0 +1,15 @@ +import icons from "lib/icons" +import PanelButton from "../PanelButton" +import options from "options" + +const { monochrome, action } = options.bar.powermenu + +export default () => PanelButton({ + window: "powermenu", + on_clicked: action.bind(), + child: Widget.Icon(icons.powermenu.shutdown), + setup: self => self.hook(monochrome, () => { + self.toggleClassName("colored", !monochrome.value) + self.toggleClassName("box") + }), +}) diff --git a/overlays/asztal/widget/bar/buttons/ScreenRecord.ts b/overlays/asztal/widget/bar/buttons/ScreenRecord.ts new file mode 100644 index 0000000..1d6eb36 --- /dev/null +++ b/overlays/asztal/widget/bar/buttons/ScreenRecord.ts @@ -0,0 +1,21 @@ +import PanelButton from "../PanelButton" +import screenrecord from "service/screenrecord" +import icons from "lib/icons" + +export default () => PanelButton({ + class_name: "recorder", + on_clicked: () => screenrecord.stop(), + visible: screenrecord.bind("recording"), + child: Widget.Box({ + children: [ + Widget.Icon(icons.recorder.recording), + Widget.Label({ + label: screenrecord.bind("timer").as(time => { + const sec = time % 60 + const min = Math.floor(time / 60) + return `${min}:${sec < 10 ? "0" + sec : sec}` + }), + }), + ], + }), +}) diff --git a/overlays/asztal/widget/bar/buttons/SysTray.ts b/overlays/asztal/widget/bar/buttons/SysTray.ts new file mode 100644 index 0000000..9f569d1 --- /dev/null +++ b/overlays/asztal/widget/bar/buttons/SysTray.ts @@ -0,0 +1,39 @@ +import { type TrayItem } from "types/service/systemtray" +import PanelButton from "../PanelButton" +import Gdk from "gi://Gdk" +import options from "options" + +const systemtray = await Service.import("systemtray") +const { ignore } = options.bar.systray + +const SysTrayItem = (item: TrayItem) => PanelButton({ + class_name: "tray-item", + child: Widget.Icon({ icon: item.bind("icon") }), + tooltip_markup: item.bind("tooltip_markup"), + setup: self => { + const { menu } = item + if (!menu) + return + + const id = menu.connect("popped-up", () => { + self.toggleClassName("active") + menu.connect("notify::visible", () => { + self.toggleClassName("active", menu.visible) + }) + menu.disconnect(id!) + }) + + self.connect("destroy", () => menu.disconnect(id)) + }, + + on_primary_click: btn => item.menu?.popup_at_widget( + btn, Gdk.Gravity.SOUTH, Gdk.Gravity.NORTH, null), + + on_secondary_click: btn => item.menu?.popup_at_widget( + btn, Gdk.Gravity.SOUTH, Gdk.Gravity.NORTH, null), +}) + +export default () => Widget.Box() + .bind("children", systemtray, "items", i => i + .filter(({ id }) => !ignore.value.includes(id)) + .map(SysTrayItem)) diff --git a/overlays/asztal/widget/bar/buttons/SystemIndicators.ts b/overlays/asztal/widget/bar/buttons/SystemIndicators.ts new file mode 100644 index 0000000..082ff81 --- /dev/null +++ b/overlays/asztal/widget/bar/buttons/SystemIndicators.ts @@ -0,0 +1,98 @@ +import PanelButton from "../PanelButton" +import icons from "lib/icons" +import asusctl from "service/asusctl" + +const notifications = await Service.import("notifications") +const bluetooth = await Service.import("bluetooth") +const audio = await Service.import("audio") +const network = await Service.import("network") +const powerprof = await Service.import("powerprofiles") + +const ProfileIndicator = () => { + const visible = asusctl.available + ? asusctl.bind("profile").as(p => p !== "Balanced") + : powerprof.bind("active_profile").as(p => p !== "balanced") + + const icon = asusctl.available + ? asusctl.bind("profile").as(p => icons.asusctl.profile[p]) + : powerprof.bind("active_profile").as(p => icons.powerprofile[p]) + + return Widget.Icon({ visible, icon }) +} + +const ModeIndicator = () => { + if (!asusctl.available) { + return Widget.Icon({ + setup(self) { + Utils.idle(() => self.visible = false) + }, + }) + } + + return Widget.Icon({ + visible: asusctl.bind("mode").as(m => m !== "Hybrid"), + icon: asusctl.bind("mode").as(m => icons.asusctl.mode[m]), + }) +} + +const MicrophoneIndicator = () => Widget.Icon() + .hook(audio, self => self.visible = + audio.recorders.length > 0 + || audio.microphone.is_muted + || false) + .hook(audio.microphone, self => { + const vol = audio.microphone.is_muted ? 0 : audio.microphone.volume + const { muted, low, medium, high } = icons.audio.mic + const cons = [[67, high], [34, medium], [1, low], [0, muted]] as const + self.icon = cons.find(([n]) => n <= vol * 100)?.[1] || "" + }) + +const DNDIndicator = () => Widget.Icon({ + visible: notifications.bind("dnd"), + icon: icons.notifications.silent, +}) + +const BluetoothIndicator = () => Widget.Overlay({ + class_name: "bluetooth", + passThrough: true, + child: Widget.Icon({ + icon: icons.bluetooth.enabled, + visible: bluetooth.bind("enabled"), + }), + overlay: Widget.Label({ + hpack: "end", + vpack: "start", + label: bluetooth.bind("connected_devices").as(c => `${c.length}`), + visible: bluetooth.bind("connected_devices").as(c => c.length > 0), + }), +}) + +const NetworkIndicator = () => Widget.Icon().hook(network, self => { + const icon = network[network.primary || "wifi"]?.icon_name + self.icon = icon || "" + self.visible = !!icon +}) + +const AudioIndicator = () => Widget.Icon() + .hook(audio.speaker, self => { + const vol = audio.speaker.is_muted ? 0 : audio.speaker.volume + const { muted, low, medium, high, overamplified } = icons.audio.volume + const cons = [[101, overamplified], [67, high], [34, medium], [1, low], [0, muted]] as const + self.icon = cons.find(([n]) => n <= vol * 100)?.[1] || "" + }) + +export default () => PanelButton({ + window: "quicksettings", + on_clicked: () => App.toggleWindow("quicksettings"), + on_scroll_up: () => audio.speaker.volume += 0.02, + on_scroll_down: () => audio.speaker.volume -= 0.02, + child: Widget.Box([ + ProfileIndicator(), + ModeIndicator(), + DNDIndicator(), + BluetoothIndicator(), + NetworkIndicator(), + AudioIndicator(), + MicrophoneIndicator(), + ]), +}) diff --git a/overlays/asztal/widget/bar/buttons/Taskbar.ts b/overlays/asztal/widget/bar/buttons/Taskbar.ts new file mode 100644 index 0000000..b9c65fa --- /dev/null +++ b/overlays/asztal/widget/bar/buttons/Taskbar.ts @@ -0,0 +1,90 @@ +import { launchApp, icon } from "lib/utils" +import icons from "lib/icons" +import options from "options" +import PanelButton from "../PanelButton" + +const hyprland = await Service.import("hyprland") +const apps = await Service.import("applications") +const { monochrome, exclusive, iconSize } = options.bar.taskbar +const { position } = options.bar + +const focus = (address: string) => hyprland.messageAsync( + `dispatch focuswindow address:${address}`) + +const DummyItem = (address: string) => Widget.Box({ + attribute: { address }, + visible: false, +}) + +const AppItem = (address: string) => { + const client = hyprland.getClient(address) + if (!client || client.class === "") + return DummyItem(address) + + const app = apps.list.find(app => app.match(client.class)) + + const btn = PanelButton({ + class_name: "panel-button", + tooltip_text: Utils.watch(client.title, hyprland, () => + hyprland.getClient(address)?.title || "", + ), + on_primary_click: () => focus(address), + on_middle_click: () => app && launchApp(app), + child: Widget.Icon({ + size: iconSize.bind(), + icon: monochrome.bind().as(m => icon( + (app?.icon_name || client.class) + (m ? "-symbolic" : ""), + icons.fallback.executable + (m ? "-symbolic" : ""), + )), + }), + }) + + return Widget.Box( + { + attribute: { address }, + visible: Utils.watch(true, [exclusive, hyprland], () => { + return exclusive.value + ? hyprland.active.workspace.id === client.workspace.id + : true + }), + }, + Widget.Overlay({ + child: btn, + pass_through: true, + overlay: Widget.Box({ + className: "indicator", + hpack: "center", + vpack: position.bind().as(p => p === "top" ? "start" : "end"), + setup: w => w.hook(hyprland, () => { + w.toggleClassName("active", hyprland.active.client.address === address) + }), + }), + }), + ) +} + +function sortItems(arr: T[]) { + return arr.sort(({ attribute: a }, { attribute: b }) => { + const aclient = hyprland.getClient(a.address)! + const bclient = hyprland.getClient(b.address)! + return aclient.workspace.id - bclient.workspace.id + }) +} + +export default () => Widget.Box({ + class_name: "taskbar", + children: sortItems(hyprland.clients.map(c => AppItem(c.address))), + setup: w => w + .hook(hyprland, (w, address?: string) => { + if (typeof address === "string") + w.children = w.children.filter(ch => ch.attribute.address !== address) + }, "client-removed") + .hook(hyprland, (w, address?: string) => { + if (typeof address === "string") + w.children = sortItems([...w.children, AppItem(address)]) + }, "client-added") + .hook(hyprland, (w, event?: string) => { + if (event === "movewindow") + w.children = sortItems(w.children) + }, "event"), +}) diff --git a/overlays/asztal/widget/bar/buttons/Workspaces.ts b/overlays/asztal/widget/bar/buttons/Workspaces.ts new file mode 100644 index 0000000..73ea347 --- /dev/null +++ b/overlays/asztal/widget/bar/buttons/Workspaces.ts @@ -0,0 +1,38 @@ +import PanelButton from "../PanelButton" +import options from "options" +import { sh, range } from "lib/utils" + +const hyprland = await Service.import("hyprland") +const { workspaces } = options.bar.workspaces + +const dispatch = (arg: string | number) => { + sh(`hyprctl dispatch workspace ${arg}`) +} + +const Workspaces = (ws: number) => Widget.Box({ + children: range(ws || 20).map(i => Widget.Label({ + attribute: i, + vpack: "center", + label: `${i}`, + setup: self => self.hook(hyprland, () => { + self.toggleClassName("active", hyprland.active.workspace.id === i) + self.toggleClassName("occupied", (hyprland.getWorkspace(i)?.windows || 0) > 0) + }), + })), + setup: box => { + if (ws === 0) { + box.hook(hyprland.active.workspace, () => box.children.map(btn => { + btn.visible = hyprland.workspaces.some(ws => ws.id === btn.attribute) + })) + } + }, +}) + +export default () => PanelButton({ + window: "overview", + class_name: "workspaces", + on_scroll_up: () => dispatch("m+1"), + on_scroll_down: () => dispatch("m-1"), + on_clicked: () => App.toggleWindow("overview"), + child: workspaces.bind().as(Workspaces), +}) diff --git a/overlays/asztal/widget/bar/screencorner.scss b/overlays/asztal/widget/bar/screencorner.scss new file mode 100644 index 0000000..d3b43a0 --- /dev/null +++ b/overlays/asztal/widget/bar/screencorner.scss @@ -0,0 +1,50 @@ +$_shadow-size: $padding; +$_radius: $radius * $hyprland-gaps-multiplier; +$_margin: 99px; + +window.screen-corner { + box.shadow { + margin-right: $_margin * -1; + margin-left: $_margin * -1; + + @if $shadows { + box-shadow: inset 0 0 $_shadow-size 0 $shadow-color; + } + + @if $bar-position =="top" { + margin-bottom: $_margin * -1; + } + + @if $bar-position =="bottom" { + margin-top: $_margin * -1; + } + } + + box.border { + @if $bar-position =="top" { + border-top: $border-width solid $bg; + } + + @if $bar-position =="bottom" { + border-bottom: $border-width solid $bg; + } + + margin-right: $_margin; + margin-left: $_margin; + } + + box.corner { + box-shadow: 0 0 0 $border-width $border-color; + } + + &.corners { + box.border { + border-radius: if($radius>0, $radius * $hyprland-gaps-multiplier, 0); + box-shadow: 0 0 0 $_radius $bg; + } + + box.corner { + border-radius: if($radius>0, $radius * $hyprland-gaps-multiplier, 0); + } + } +} diff --git a/overlays/asztal/widget/datemenu/DateColumn.ts b/overlays/asztal/widget/datemenu/DateColumn.ts new file mode 100644 index 0000000..94e7051 --- /dev/null +++ b/overlays/asztal/widget/datemenu/DateColumn.ts @@ -0,0 +1,37 @@ +import { clock, uptime } from "lib/variables" + +function up(up: number) { + const h = Math.floor(up / 60) + const m = Math.floor(up % 60) + return `uptime: ${h}:${m < 10 ? "0" + m : m}` +} + +export default () => Widget.Box({ + vertical: true, + class_name: "date-column vertical", + children: [ + Widget.Box({ + class_name: "clock-box", + vertical: true, + children: [ + Widget.Label({ + class_name: "clock", + label: clock.bind().as(t => t.format("%H:%M")!), + }), + Widget.Label({ + class_name: "uptime", + label: uptime.bind().as(up), + }), + ], + }), + Widget.Box({ + class_name: "calendar", + children: [ + Widget.Calendar({ + hexpand: true, + hpack: "center", + }), + ], + }), + ], +}) diff --git a/overlays/asztal/widget/datemenu/DateMenu.ts b/overlays/asztal/widget/datemenu/DateMenu.ts new file mode 100644 index 0000000..f7fdf6d --- /dev/null +++ b/overlays/asztal/widget/datemenu/DateMenu.ts @@ -0,0 +1,36 @@ +import PopupWindow from "widget/PopupWindow" +import NotificationColumn from "./NotificationColumn" +import DateColumn from "./DateColumn" +import options from "options" + +const { bar, datemenu } = options +const pos = bar.position.bind() +const layout = Utils.derive([bar.position, datemenu.position], (bar, qs) => + `${bar}-${qs}` as const, +) + +const Settings = () => Widget.Box({ + class_name: "datemenu horizontal", + vexpand: false, + children: [ + NotificationColumn(), + Widget.Separator({ orientation: 1 }), + DateColumn(), + ], +}) + +const DateMenu = () => PopupWindow({ + name: "datemenu", + exclusivity: "exclusive", + transition: pos.as(pos => pos === "top" ? "slide_down" : "slide_up"), + layout: layout.value, + child: Settings(), +}) + +export function setupDateMenu() { + App.addWindow(DateMenu()) + layout.connect("changed", () => { + App.removeWindow("datemenu") + App.addWindow(DateMenu()) + }) +} diff --git a/overlays/asztal/widget/datemenu/NotificationColumn.ts b/overlays/asztal/widget/datemenu/NotificationColumn.ts new file mode 100644 index 0000000..07d6829 --- /dev/null +++ b/overlays/asztal/widget/datemenu/NotificationColumn.ts @@ -0,0 +1,113 @@ +import { type Notification as Notif } from "types/service/notifications" +import Notification from "widget/notifications/Notification" +import options from "options" +import icons from "lib/icons" + +const notifications = await Service.import("notifications") +const notifs = notifications.bind("notifications") + +const Animated = (n: Notif) => Widget.Revealer({ + transition_duration: options.transition.value, + transition: "slide_down", + child: Notification(n), + setup: self => Utils.timeout(options.transition.value, () => { + if (!self.is_destroyed) + self.reveal_child = true + }), +}) + +const ClearButton = () => Widget.Button({ + on_clicked: notifications.clear, + sensitive: notifs.as(n => n.length > 0), + child: Widget.Box({ + children: [ + Widget.Label("Clear "), + Widget.Icon({ + icon: notifs.as(n => icons.trash[n.length > 0 ? "full" : "empty"]), + }), + ], + }), +}) + +const Header = () => Widget.Box({ + class_name: "header", + children: [ + Widget.Label({ label: "Notifications", hexpand: true, xalign: 0 }), + ClearButton(), + ], +}) + +const NotificationList = () => { + const map: Map> = new Map + const box = Widget.Box({ + vertical: true, + children: notifications.notifications.map(n => { + const w = Animated(n) + map.set(n.id, w) + return w + }), + visible: notifs.as(n => n.length > 0), + }) + + function remove(_: unknown, id: number) { + const n = map.get(id) + if (n) { + n.reveal_child = false + Utils.timeout(options.transition.value, () => { + n.destroy() + map.delete(id) + }) + } + } + + return box + .hook(notifications, remove, "closed") + .hook(notifications, (_, id: number) => { + if (id !== undefined) { + if (map.has(id)) + remove(null, id) + + const n = notifications.getNotification(id)! + + const w = Animated(n) + map.set(id, w) + box.children = [w, ...box.children] + } + }, "notified") +} + +const Placeholder = () => Widget.Box({ + class_name: "placeholder", + vertical: true, + vpack: "center", + hpack: "center", + vexpand: true, + hexpand: true, + visible: notifs.as(n => n.length === 0), + children: [ + Widget.Icon(icons.notifications.silent), + Widget.Label("Your inbox is empty"), + ], +}) + +export default () => Widget.Box({ + class_name: "notifications", + css: options.notifications.width.bind().as(w => `min-width: ${w}px`), + vertical: true, + children: [ + Header(), + Widget.Scrollable({ + vexpand: true, + hscroll: "never", + class_name: "notification-scrollable", + child: Widget.Box({ + class_name: "notification-list vertical", + vertical: true, + children: [ + NotificationList(), + Placeholder(), + ], + }), + }), + ], +}) diff --git a/overlays/asztal/widget/datemenu/datemenu.scss b/overlays/asztal/widget/datemenu/datemenu.scss new file mode 100644 index 0000000..6fd9257 --- /dev/null +++ b/overlays/asztal/widget/datemenu/datemenu.scss @@ -0,0 +1,110 @@ +@import "../notifications/notifications.scss"; + +@mixin calendar { + @include widget; + padding: $padding*2 $padding*2 0; + + calendar { + all: unset; + + &.button { + @include button($flat: true); + } + + &:selected { + box-shadow: inset 0 -8px 0 0 transparentize($primary-bg, 0.5), + inset 0 0 0 1px $primary-bg; + border-radius: $radius*0.6; + } + + &.header { + background-color: transparent; + border: none; + color: transparentize($fg, 0.5); + } + + &.highlight { + background-color: transparent; + color: transparentize($primary-bg, 0.5); + } + + &:indeterminate { + color: transparentize($fg, 0.9); + } + + font-size: 1.1em; + padding: .2em; + } +} + +window#datemenu .datemenu { + @include floating-widget; + + .notifications { + .header { + margin-bottom: $spacing; + margin-right: $spacing; + + >label { + margin-left: $radius * .5; + } + + button { + @include button; + padding: $padding*.7 $padding; + } + } + + .notification-scrollable { + @include scrollable($top: true, $bottom: true); + } + + .notification-list { + margin-right: $spacing; + } + + .notification { + @include notification; + @include widget; + padding: $padding; + margin-bottom: $spacing; + } + + .placeholder { + image { + font-size: 7em; + } + + label { + font-size: 1.2em; + } + } + } + + + separator { + background-color: $popover-border-color; + border-radius: $radius; + margin-right: $spacing; + } + + .datemenu { + @include spacing; + } + + .clock-box { + padding: $padding; + + .clock { + font-size: 5em; + } + + .uptime { + color: transparentize($fg, 0.2); + } + } + + .calendar { + @include calendar; + } +} diff --git a/overlays/asztal/widget/desktop/Desktop.ts b/overlays/asztal/widget/desktop/Desktop.ts new file mode 100644 index 0000000..f711967 --- /dev/null +++ b/overlays/asztal/widget/desktop/Desktop.ts @@ -0,0 +1,40 @@ +import options from "options" +import { matugen } from "lib/matugen" +const mpris = await Service.import("mpris") + +const pref = () => options.bar.media.preferred.value + +export default (monitor: number) => Widget.Window({ + monitor, + layer: "bottom", + name: `desktop${monitor}`, + class_name: "desktop", + anchor: ["top", "bottom", "left", "right"], + child: Widget.Box({ + expand: true, + css: options.theme.dark.primary.bg.bind().as(c => ` + transition: 500ms; + background-color: ${c}`), + child: Widget.Box({ + class_name: "wallpaper", + expand: true, + vpack: "center", + hpack: "center", + setup: self => self + .hook(mpris, () => { + const img = mpris.getPlayer(pref())!.cover_path + matugen("image", img) + Utils.timeout(500, () => self.css = ` + background-image: url('${img}'); + background-size: contain; + background-repeat: no-repeat; + transition: 200ms; + min-width: 700px; + min-height: 700px; + border-radius: 30px; + box-shadow: 25px 25px 30px 0 rgba(0,0,0,0.5);`, + ) + }), + }), + }), +}) diff --git a/overlays/asztal/widget/launcher/AppLauncher.ts b/overlays/asztal/widget/launcher/AppLauncher.ts new file mode 100644 index 0000000..131fc85 --- /dev/null +++ b/overlays/asztal/widget/launcher/AppLauncher.ts @@ -0,0 +1,130 @@ +import { type Application } from "types/service/applications" +import { launchApp, icon } from "lib/utils" +import options from "options" +import icons from "lib/icons" + +const apps = await Service.import("applications") +const { query } = apps +const { iconSize } = options.launcher.apps + +const QuickAppButton = (app: Application) => Widget.Button({ + hexpand: true, + tooltip_text: app.name, + on_clicked: () => { + App.closeWindow("launcher") + launchApp(app) + }, + child: Widget.Icon({ + size: iconSize.bind(), + icon: icon(app.icon_name, icons.fallback.executable), + }), +}) + +const AppItem = (app: Application) => { + const title = Widget.Label({ + class_name: "title", + label: app.name, + hexpand: true, + xalign: 0, + vpack: "center", + truncate: "end", + }) + + const description = Widget.Label({ + class_name: "description", + label: app.description || "", + hexpand: true, + wrap: true, + max_width_chars: 30, + xalign: 0, + justification: "left", + vpack: "center", + }) + + const appicon = Widget.Icon({ + icon: icon(app.icon_name, icons.fallback.executable), + size: iconSize.bind(), + }) + + const textBox = Widget.Box({ + vertical: true, + vpack: "center", + children: app.description ? [title, description] : [title], + }) + + return Widget.Button({ + class_name: "app-item", + attribute: { app }, + child: Widget.Box({ + children: [appicon, textBox], + }), + on_clicked: () => { + App.closeWindow("launcher") + launchApp(app) + }, + }) +} +export function Favorites() { + const favs = options.launcher.apps.favorites.bind() + return Widget.Revealer({ + visible: favs.as(f => f.length > 0), + child: Widget.Box({ + vertical: true, + children: favs.as(favs => favs.flatMap(fs => [ + Widget.Separator(), + Widget.Box({ + class_name: "quicklaunch horizontal", + children: fs + .map(f => query(f)?.[0]) + .filter(f => f) + .map(QuickAppButton), + }), + ])), + }), + }) +} + +export function Launcher() { + const applist = Variable(query("")) + const max = options.launcher.apps.max + let first = applist.value[0] + + function SeparatedAppItem(app: Application) { + return Widget.Revealer( + { attribute: { app } }, + Widget.Box( + { vertical: true }, + Widget.Separator(), + AppItem(app), + ), + ) + } + + const list = Widget.Box({ + vertical: true, + children: applist.bind().as(list => list.map(SeparatedAppItem)), + setup: self => self + .hook(apps, () => applist.value = query(""), "notify::frequents"), + }) + + return Object.assign(list, { + filter(text: string | null) { + first = query(text || "")[0] + list.children.reduce((i, item) => { + if (!text || i >= max.value) { + item.reveal_child = false + return i + } + if (item.attribute.app.match(text)) { + item.reveal_child = true + return ++i + } + item.reveal_child = false + return i + }, 0) + }, + launchFirst() { + launchApp(first) + }, + }) +} diff --git a/overlays/asztal/widget/launcher/Launcher.ts b/overlays/asztal/widget/launcher/Launcher.ts new file mode 100644 index 0000000..da33dd1 --- /dev/null +++ b/overlays/asztal/widget/launcher/Launcher.ts @@ -0,0 +1,139 @@ +import { type Binding } from "lib/utils" +import PopupWindow, { Padding } from "widget/PopupWindow" +import icons from "lib/icons" +import options from "options" +import nix from "service/nix" +import * as AppLauncher from "./AppLauncher" +import * as NixRun from "./NixRun" +import * as ShRun from "./ShRun" + +const { width, margin } = options.launcher +const isnix = nix.available + +function Launcher() { + const favs = AppLauncher.Favorites() + const applauncher = AppLauncher.Launcher() + const sh = ShRun.ShRun() + const shicon = ShRun.Icon() + const nix = NixRun.NixRun() + const nixload = NixRun.Spinner() + + function HelpButton(cmd: string, desc: string | Binding) { + return Widget.Box( + { vertical: true }, + Widget.Separator(), + Widget.Button( + { + class_name: "help", + on_clicked: () => { + entry.grab_focus() + entry.text = `:${cmd} ` + entry.set_position(-1) + }, + }, + Widget.Box([ + Widget.Label({ + class_name: "name", + label: `:${cmd}`, + }), + Widget.Label({ + hexpand: true, + hpack: "end", + class_name: "description", + label: desc, + }), + ]), + ), + ) + } + + const help = Widget.Revealer({ + child: Widget.Box( + { vertical: true }, + HelpButton("sh", "run a binary"), + isnix ? HelpButton("nx", options.launcher.nix.pkgs.bind().as(pkg => + `run a nix package from ${pkg}`, + )) : Widget.Box(), + ), + }) + + const entry = Widget.Entry({ + hexpand: true, + primary_icon_name: icons.ui.search, + on_accept: ({ text }) => { + if (text?.startsWith(":nx")) + nix.run(text.substring(3)) + else if (text?.startsWith(":sh")) + sh.run(text.substring(3)) + else + applauncher.launchFirst() + + App.toggleWindow("launcher") + entry.text = "" + }, + on_change: ({ text }) => { + text ||= "" + favs.reveal_child = text === "" + help.reveal_child = text.split(" ").length === 1 && text?.startsWith(":") + + if (text?.startsWith(":nx")) + nix.filter(text.substring(3)) + else + nix.filter("") + + if (text?.startsWith(":sh")) + sh.filter(text.substring(3)) + else + sh.filter("") + + if (!text?.startsWith(":")) + applauncher.filter(text) + }, + }) + + function focus() { + entry.text = "Search" + entry.set_position(-1) + entry.select_region(0, -1) + entry.grab_focus() + favs.reveal_child = true + } + + const layout = Widget.Box({ + css: width.bind().as(v => `min-width: ${v}pt;`), + class_name: "launcher", + vertical: true, + vpack: "start", + setup: self => self.hook(App, (_, win, visible) => { + if (win !== "launcher") + return + + entry.text = "" + if (visible) + focus() + }), + children: [ + Widget.Box([entry, nixload, shicon]), + favs, + help, + applauncher, + nix, + sh, + ], + }) + + return Widget.Box( + { vertical: true, css: "padding: 1px" }, + Padding("applauncher", { + css: margin.bind().as(v => `min-height: ${v}pt;`), + vexpand: false, + }), + layout, + ) +} + +export default () => PopupWindow({ + name: "launcher", + layout: "top", + child: Launcher(), +}) diff --git a/overlays/asztal/widget/launcher/NixRun.ts b/overlays/asztal/widget/launcher/NixRun.ts new file mode 100644 index 0000000..cec9e09 --- /dev/null +++ b/overlays/asztal/widget/launcher/NixRun.ts @@ -0,0 +1,118 @@ +import icons from "lib/icons" +import nix, { type Nixpkg } from "service/nix" + +const iconVisible = Variable(false) + +function Item(pkg: Nixpkg) { + const name = Widget.Label({ + class_name: "name", + label: pkg.name.split(".").at(-1), + }) + + const subpkg = pkg.name.includes(".") ? Widget.Label({ + class_name: "description", + hpack: "end", + hexpand: true, + label: ` ${pkg.name.split(".").slice(0, -1).join(".")}`, + }) : null + + const version = Widget.Label({ + class_name: "version", + label: pkg.version, + hexpand: true, + hpack: "end", + }) + + const description = pkg.description ? Widget.Label({ + class_name: "description", + label: pkg.description, + justification: "left", + wrap: true, + hpack: "start", + max_width_chars: 40, + }) : null + + return Widget.Box( + { + attribute: { name: pkg.name }, + vertical: true, + }, + Widget.Separator(), + Widget.Button( + { + class_name: "nix-item", + on_clicked: () => { + nix.run(pkg.name) + App.closeWindow("launcher") + }, + }, + Widget.Box( + { vertical: true }, + Widget.Box([name, version]), + Widget.Box([ + description as ReturnType, + subpkg as ReturnType, + ]), + ), + ), + ) +} + +export function Spinner() { + const icon = Widget.Icon({ + icon: icons.nix.nix, + class_name: "spinner", + css: ` + @keyframes spin { + to { -gtk-icon-transform: rotate(1turn); } + } + + image.spinning { + animation-name: spin; + animation-duration: 1s; + animation-timing-function: linear; + animation-iteration-count: infinite; + } + `, + setup: self => self.hook(nix, () => { + self.toggleClassName("spinning", !nix.ready) + }), + }) + + return Widget.Revealer({ + transition: "slide_left", + child: icon, + reveal_child: Utils.merge([ + nix.bind("ready"), + iconVisible.bind(), + ], (ready, show) => !ready || show), + }) +} + +export function NixRun() { + const list = Widget.Box>({ + vertical: true, + }) + + const revealer = Widget.Revealer({ + child: list, + }) + + async function filter(term: string) { + iconVisible.value = Boolean(term) + + if (!term) + revealer.reveal_child = false + + if (term.trim()) { + const found = await nix.query(term) + list.children = found.map(k => Item(nix.db[k])) + revealer.reveal_child = true + } + } + + return Object.assign(revealer, { + filter, + run: nix.run, + }) +} diff --git a/overlays/asztal/widget/launcher/ShRun.ts b/overlays/asztal/widget/launcher/ShRun.ts new file mode 100644 index 0000000..c4215ef --- /dev/null +++ b/overlays/asztal/widget/launcher/ShRun.ts @@ -0,0 +1,89 @@ +import icons from "lib/icons" +import options from "options" +import { bash, dependencies } from "lib/utils" + +const iconVisible = Variable(false) + +const MAX = options.launcher.sh.max +const BINS = `${Utils.CACHE_DIR}/binaries` +bash("{ IFS=:; ls -H $PATH; } | sort ") + .then(bins => Utils.writeFile(bins, BINS)) + +async function query(filter: string) { + if (!dependencies("fzf")) + return [] as string[] + + return bash(`cat ${BINS} | fzf -f ${filter} | head -n ${MAX}`) + .then(str => Array.from(new Set(str.split("\n").filter(i => i)).values())) + .catch(err => { print(err); return [] }) +} + +function run(args: string) { + Utils.execAsync(args) + .then(out => { + print(`:sh ${args.trim()}:`) + print(out) + }) + .catch(err => { + Utils.notify("ShRun Error", err, icons.app.terminal) + }) +} + +function Item(bin: string) { + return Widget.Box( + { + attribute: { bin }, + vertical: true, + }, + Widget.Separator(), + Widget.Button({ + child: Widget.Label({ + label: bin, + hpack: "start", + }), + class_name: "sh-item", + on_clicked: () => { + Utils.execAsync(bin) + App.closeWindow("launcher") + }, + }), + ) +} + +export function Icon() { + const icon = Widget.Icon({ + icon: icons.app.terminal, + class_name: "spinner", + }) + + return Widget.Revealer({ + transition: "slide_left", + child: icon, + reveal_child: iconVisible.bind(), + }) +} + +export function ShRun() { + const list = Widget.Box>({ + vertical: true, + }) + + const revealer = Widget.Revealer({ + child: list, + }) + + async function filter(term: string) { + iconVisible.value = Boolean(term) + + if (!term) + revealer.reveal_child = false + + if (term.trim()) { + const found = await query(term) + list.children = found.map(Item) + revealer.reveal_child = true + } + } + + return Object.assign(revealer, { filter, run }) +} diff --git a/overlays/asztal/widget/launcher/launcher.scss b/overlays/asztal/widget/launcher/launcher.scss new file mode 100644 index 0000000..926abc3 --- /dev/null +++ b/overlays/asztal/widget/launcher/launcher.scss @@ -0,0 +1,143 @@ +@use "sass:math"; +@use "sass:color"; + +window#launcher .launcher { + @include floating_widget; + + .quicklaunch { + @include spacing; + + button { + @include button($flat: true); + padding: $padding; + } + } + + entry { + @include button; + padding: $padding; + margin: $spacing; + + selection { + color: color.mix($fg, $bg, 50%); + background-color: transparent; + } + + label, + image { + color: $fg; + } + } + + image.spinner { + color: $primary-bg; + margin-right: $spacing; + } + + separator { + margin: 4pt 0; + background-color: $popover-border-color; + } + + button.app-item { + @include button($flat: true, $reactive: false); + + >box { + @include spacing(0.5); + } + + transition: $transition; + padding: $padding; + + label { + transition: $transition; + + &.title { + color: $fg; + } + + &.description { + color: transparentize($fg, 0.3); + } + } + + image { + transition: $transition; + } + + &:hover, + &:focus { + .title { + color: $primary-bg; + } + + .description { + color: transparentize($primary-bg, .4); + } + + image { + -gtk-icon-shadow: 2px 2px $primary-bg; + } + } + + &:active { + background-color: transparentize($primary-bg, 0.5); + border-radius: $radius; + box-shadow: inset 0 0 0 $border-width $border-color; + + .title { + color: $fg; + } + } + } + + button.help, + button.nix-item { + @include button($flat: true, $reactive: false); + padding: 0 ($padding * .5); + + label { + transition: $transition; + color: $fg; + } + + .name { + font-size: 1.2em; + font-weight: bold; + } + + .description { + color: transparentize($fg, .3) + } + + &:hover, + &:focus { + label { + text-shadow: $text-shadow; + } + + .name, + .version { + color: $primary-bg; + } + + .description { + color: transparentize($primary-bg, .3) + } + } + } + + button.sh-item { + @include button($flat: true, $reactive: false); + padding: 0 ($padding * .5); + + transition: $transition; + color: $fg; + + &:hover, + &:focus { + color: $primary-bg; + text-shadow: $text-shadow; + } + } +} diff --git a/overlays/asztal/widget/notifications/Notification.ts b/overlays/asztal/widget/notifications/Notification.ts new file mode 100644 index 0000000..f05fba2 --- /dev/null +++ b/overlays/asztal/widget/notifications/Notification.ts @@ -0,0 +1,138 @@ +import { type Notification } from "types/service/notifications" +import GLib from "gi://GLib" +import icons from "lib/icons" + +const time = (time: number, format = "%H:%M") => GLib.DateTime + .new_from_unix_local(time) + .format(format) + +const NotificationIcon = ({ app_entry, app_icon, image }: Notification) => { + if (image) { + return Widget.Box({ + vpack: "start", + hexpand: false, + class_name: "icon img", + css: ` + background-image: url("${image}"); + background-size: cover; + background-repeat: no-repeat; + background-position: center; + min-width: 78px; + min-height: 78px; + `, + }) + } + + let icon = icons.fallback.notification + if (Utils.lookUpIcon(app_icon)) + icon = app_icon + + if (Utils.lookUpIcon(app_entry || "")) + icon = app_entry || "" + + return Widget.Box({ + vpack: "start", + hexpand: false, + class_name: "icon", + css: ` + min-width: 78px; + min-height: 78px; + `, + child: Widget.Icon({ + icon, + size: 58, + hpack: "center", hexpand: true, + vpack: "center", vexpand: true, + }), + }) +} + +export default (notification: Notification) => { + const content = Widget.Box({ + class_name: "content", + children: [ + NotificationIcon(notification), + Widget.Box({ + hexpand: true, + vertical: true, + children: [ + Widget.Box({ + children: [ + Widget.Label({ + class_name: "title", + xalign: 0, + justification: "left", + hexpand: true, + max_width_chars: 24, + truncate: "end", + wrap: true, + label: notification.summary.trim(), + use_markup: true, + }), + Widget.Label({ + class_name: "time", + vpack: "start", + label: time(notification.time), + }), + Widget.Button({ + class_name: "close-button", + vpack: "start", + child: Widget.Icon("window-close-symbolic"), + on_clicked: notification.close, + }), + ], + }), + Widget.Label({ + class_name: "description", + 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({ + class_name: "action-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({ + class_name: `notification ${notification.urgency}`, + child: eventbox, + }) +} diff --git a/overlays/asztal/widget/notifications/NotificationPopups.ts b/overlays/asztal/widget/notifications/NotificationPopups.ts new file mode 100644 index 0000000..a4a2b54 --- /dev/null +++ b/overlays/asztal/widget/notifications/NotificationPopups.ts @@ -0,0 +1,90 @@ +import Notification from "./Notification" +import options from "options" + +const notifications = await Service.import("notifications") +const { transition } = options +const { position } = options.notifications +const { timeout, idle } = Utils + +function Animated(id: number) { + const n = notifications.getNotification(id)! + const widget = Notification(n) + + const inner = Widget.Revealer({ + transition: "slide_left", + transition_duration: transition.value, + child: widget, + }) + + const outer = Widget.Revealer({ + transition: "slide_down", + transition_duration: transition.value, + child: inner, + }) + + const box = Widget.Box({ + hpack: "end", + child: outer, + }) + + idle(() => { + outer.reveal_child = true + timeout(transition.value, () => { + inner.reveal_child = true + }) + }) + + return Object.assign(box, { + dismiss() { + inner.reveal_child = false + timeout(transition.value, () => { + outer.reveal_child = false + timeout(transition.value, () => { + box.destroy() + }) + }) + }, + }) +} + +function PopupList() { + const map: Map> = new Map + const box = Widget.Box({ + hpack: "end", + vertical: true, + css: options.notifications.width.bind().as(w => `min-width: ${w}px;`), + }) + + function remove(_: unknown, id: number) { + map.get(id)?.dismiss() + map.delete(id) + } + + return box + .hook(notifications, (_, id: number) => { + if (id !== undefined) { + if (map.has(id)) + remove(null, id) + + if (notifications.dnd) + return + + const w = Animated(id) + map.set(id, w) + box.children = [w, ...box.children] + } + }, "notified") + .hook(notifications, remove, "dismissed") + .hook(notifications, remove, "closed") +} + +export default (monitor: number) => Widget.Window({ + monitor, + name: `notifications${monitor}`, + anchor: position.bind(), + class_name: "notifications", + child: Widget.Box({ + css: "padding: 2px;", + child: PopupList(), + }), +}) diff --git a/overlays/asztal/widget/notifications/notifications.scss b/overlays/asztal/widget/notifications/notifications.scss new file mode 100644 index 0000000..a79d9f2 --- /dev/null +++ b/overlays/asztal/widget/notifications/notifications.scss @@ -0,0 +1,79 @@ +@mixin notification() { + &.critical { + box-shadow: inset 0 0 .5em 0 $error-bg; + } + + &:hover button.close-button { + @include button-hover; + background-color: transparentize($error-bg, .5); + } + + .content { + .title { + margin-right: $spacing; + color: $fg; + font-size: 1.1em; + } + + .time { + color: transparentize($fg, .2); + } + + .description { + font-size: .9em; + color: transparentize($fg, .2); + } + + .icon { + border-radius: $radius*0.8; + margin-right: $spacing; + + &.img { + border: $border; + } + } + } + + box.actions { + @include spacing(0.5); + margin-top: $spacing; + + button { + @include button; + border-radius: $radius*0.8; + font-size: 1.2em; + padding: $padding * 0.7; + } + } + + button.close-button { + @include button($flat: true); + margin-left: $spacing / 2; + border-radius: $radius*0.8; + min-width: 1.2em; + min-height: 1.2em; + + &:hover { + background-color: transparentize($error-bg, .2); + } + + &:active { + background-image: none; + background-color: $error-bg; + } + } +} + +window.notifications { + @include unset; + + .notification { + @include notification; + @include floating-widget; + border-radius: $radius; + + .description { + min-width: 350px; + } + } +} diff --git a/overlays/asztal/widget/osd/OSD.ts b/overlays/asztal/widget/osd/OSD.ts new file mode 100644 index 0000000..467e6dc --- /dev/null +++ b/overlays/asztal/widget/osd/OSD.ts @@ -0,0 +1,111 @@ +import { icon } from "lib/utils" +import icons from "lib/icons" +import Progress from "./Progress" +import brightness from "service/brightness" +import options from "options" + +const audio = await Service.import("audio") +const { progress, microphone } = options.osd + +const DELAY = 2500 + +function OnScreenProgress(vertical: boolean) { + const indicator = Widget.Icon({ + size: 42, + vpack: "start", + }) + const progress = Progress({ + vertical, + width: vertical ? 42 : 300, + height: vertical ? 300 : 42, + child: indicator, + }) + + const revealer = Widget.Revealer({ + transition: "slide_left", + child: progress, + }) + + let count = 0 + function show(value: number, icon: string) { + revealer.reveal_child = true + indicator.icon = icon + progress.setValue(value) + count++ + Utils.timeout(DELAY, () => { + count-- + + if (count === 0) + revealer.reveal_child = false + }) + } + + return revealer + .hook(brightness, () => show( + brightness.screen, + icons.brightness.screen, + ), "notify::screen") + .hook(brightness, () => show( + brightness.kbd, + icons.brightness.keyboard, + ), "notify::kbd") + .hook(audio.speaker, () => show( + audio.speaker.volume, + icon(audio.speaker.icon_name || "", icons.audio.type.speaker), + ), "notify::volume") +} + +function MicrophoneMute() { + const icon = Widget.Icon({ + class_name: "microphone", + }) + + const revealer = Widget.Revealer({ + transition: "slide_up", + child: icon, + }) + + let count = 0 + let mute = audio.microphone.stream?.is_muted ?? false + + return revealer.hook(audio.microphone, () => Utils.idle(() => { + if (mute !== audio.microphone.stream?.is_muted) { + mute = audio.microphone.stream!.is_muted + icon.icon = icons.audio.mic[mute ? "muted" : "high"] + revealer.reveal_child = true + count++ + + Utils.timeout(DELAY, () => { + count-- + if (count === 0) + revealer.reveal_child = false + }) + } + })) +} + +export default (monitor: number) => Widget.Window({ + monitor, + name: `indicator${monitor}`, + class_name: "indicator", + layer: "overlay", + click_through: true, + anchor: ["right", "left", "top", "bottom"], + child: Widget.Box({ + css: "padding: 2px;", + expand: true, + child: Widget.Overlay( + { child: Widget.Box({ expand: true }) }, + Widget.Box({ + hpack: progress.pack.h.bind(), + vpack: progress.pack.v.bind(), + child: progress.vertical.bind().as(OnScreenProgress), + }), + Widget.Box({ + hpack: microphone.pack.h.bind(), + vpack: microphone.pack.v.bind(), + child: MicrophoneMute(), + }), + ), + }), +}) diff --git a/overlays/asztal/widget/osd/Progress.ts b/overlays/asztal/widget/osd/Progress.ts new file mode 100644 index 0000000..bcf27da --- /dev/null +++ b/overlays/asztal/widget/osd/Progress.ts @@ -0,0 +1,74 @@ +import type Gtk from "gi://Gtk?version=3.0" +import GLib from "gi://GLib?version=2.0" +import { range } from "lib/utils" +import options from "options" + +type ProgressProps = { + height?: number + width?: number + vertical?: boolean + child: Gtk.Widget +} + +export default ({ + height = 18, + width = 180, + vertical = false, + child, +}: ProgressProps) => { + const fill = Widget.Box({ + class_name: "fill", + hexpand: vertical, + vexpand: !vertical, + hpack: vertical ? "fill" : "start", + vpack: vertical ? "end" : "fill", + child, + }) + + const container = Widget.Box({ + class_name: "progress", + child: fill, + css: ` + min-width: ${width}px; + min-height: ${height}px; + `, + }) + + let fill_size = 0 + let animations: number[] = [] + + return Object.assign(container, { + setValue(value: number) { + if (value < 0) + return + + if (animations.length > 0) { + for (const id of animations) + GLib.source_remove(id) + + animations = [] + } + + const axis = vertical ? "height" : "width" + const axisv = vertical ? height : width + const min = vertical ? width : height + const preferred = (axisv - min) * value + min + + if (!fill_size) { + fill_size = preferred + fill.css = `min-${axis}: ${preferred}px;` + return + } + + const frames = options.transition.value / 10 + const goal = preferred - fill_size + const step = goal / frames + + animations = range(frames, 0).map(i => Utils.timeout(5 * i, () => { + fill_size += step + fill.css = `min-${axis}: ${fill_size}px` + animations.shift() + })) + }, + }) +} diff --git a/overlays/asztal/widget/osd/osd.scss b/overlays/asztal/widget/osd/osd.scss new file mode 100644 index 0000000..111a486 --- /dev/null +++ b/overlays/asztal/widget/osd/osd.scss @@ -0,0 +1,26 @@ +window.indicator { + .progress { + @include floating-widget; + padding: $padding * .5; + border-radius: if($radius >0, calc($radius + $padding*.5), 0); + @debug $radius; + + .fill { + border-radius: $radius; + background-color: $primary-bg; + color: $primary-fg; + + image { + -gtk-icon-transform: scale(0.7); + } + } + } + + .microphone { + @include floating-widget; + margin: $spacing * 2; + padding: $popover-padding * 2; + font-size: 58px; + color: transparentize($fg, .1) + } +} diff --git a/overlays/asztal/widget/overview/Overview.ts b/overlays/asztal/widget/overview/Overview.ts new file mode 100644 index 0000000..8911920 --- /dev/null +++ b/overlays/asztal/widget/overview/Overview.ts @@ -0,0 +1,41 @@ +import PopupWindow from "widget/PopupWindow" +import Workspace from "./Workspace" +import options from "options" +import { range } from "lib/utils" + +const hyprland = await Service.import("hyprland") + +const Overview = (ws: number) => Widget.Box({ + class_name: "overview horizontal", + children: ws > 0 + ? range(ws).map(Workspace) + : hyprland.workspaces + .map(({ id }) => Workspace(id)) + .sort((a, b) => a.attribute.id - b.attribute.id), + + setup: w => { + if (ws > 0) + return + + w.hook(hyprland, (w, id?: string) => { + if (id === undefined) + return + + w.children = w.children + .filter(ch => ch.attribute.id !== Number(id)) + }, "workspace-removed") + w.hook(hyprland, (w, id?: string) => { + if (id === undefined) + return + + w.children = [...w.children, Workspace(Number(id))] + .sort((a, b) => a.attribute.id - b.attribute.id) + }, "workspace-added") + }, +}) + +export default () => PopupWindow({ + name: "overview", + layout: "center", + child: options.overview.workspaces.bind().as(Overview), +}) diff --git a/overlays/asztal/widget/overview/Window.ts b/overlays/asztal/widget/overview/Window.ts new file mode 100644 index 0000000..02f71eb --- /dev/null +++ b/overlays/asztal/widget/overview/Window.ts @@ -0,0 +1,48 @@ +import { type Client } from "types/service/hyprland" +import { createSurfaceFromWidget, icon } from "lib/utils" +import Gdk from "gi://Gdk" +import Gtk from "gi://Gtk?version=3.0" +import options from "options" +import icons from "lib/icons" + +const monochrome = options.overview.monochromeIcon +const TARGET = [Gtk.TargetEntry.new("text/plain", Gtk.TargetFlags.SAME_APP, 0)] +const hyprland = await Service.import("hyprland") +const apps = await Service.import("applications") +const dispatch = (args: string) => hyprland.messageAsync(`dispatch ${args}`) + +export default ({ address, size: [w, h], class: c, title }: Client) => Widget.Button({ + class_name: "client", + attribute: { address }, + tooltip_text: `${title}`, + child: Widget.Icon({ + css: options.overview.scale.bind().as(v => ` + min-width: ${(v / 100) * w}px; + min-height: ${(v / 100) * h}px; + `), + icon: monochrome.bind().as(m => { + const app = apps.list.find(app => app.match(c)) + if (!app) + return icons.fallback.executable + (m ? "-symbolic" : "") + + + return icon( + app.icon_name + (m ? "-symbolic" : ""), + icons.fallback.executable + (m ? "-symbolic" : ""), + ) + }), + }), + on_secondary_click: () => dispatch(`closewindow address:${address}`), + on_clicked: () => { + dispatch(`focuswindow address:${address}`) + App.closeWindow("overview") + }, + setup: btn => btn + .on("drag-data-get", (_w, _c, data) => data.set_text(address, address.length)) + .on("drag-begin", (_, context) => { + Gtk.drag_set_icon_surface(context, createSurfaceFromWidget(btn)) + btn.toggleClassName("hidden", true) + }) + .on("drag-end", () => btn.toggleClassName("hidden", false)) + .drag_source_set(Gdk.ModifierType.BUTTON1_MASK, TARGET, Gdk.DragAction.COPY), +}) diff --git a/overlays/asztal/widget/overview/Workspace.ts b/overlays/asztal/widget/overview/Workspace.ts new file mode 100644 index 0000000..1b8d60b --- /dev/null +++ b/overlays/asztal/widget/overview/Workspace.ts @@ -0,0 +1,76 @@ +import Window from "./Window" +import Gdk from "gi://Gdk" +import Gtk from "gi://Gtk?version=3.0" +import options from "options" + +const TARGET = [Gtk.TargetEntry.new("text/plain", Gtk.TargetFlags.SAME_APP, 0)] +const scale = (size: number) => (options.overview.scale.value / 100) * size +const hyprland = await Service.import("hyprland") + +const dispatch = (args: string) => hyprland.messageAsync(`dispatch ${args}`) + +const size = (id: number) => { + const def = { h: 1080, w: 1920 } + const ws = hyprland.getWorkspace(id) + if (!ws) + return def + + const mon = hyprland.getMonitor(ws.monitorID) + return mon ? { h: mon.height, w: mon.width } : def +} + +export default (id: number) => { + const fixed = Widget.Fixed() + + // TODO: early return if position is unchaged + async function update() { + const json = await hyprland.messageAsync("j/clients").catch(() => null) + if (!json) + return + + fixed.get_children().forEach(ch => ch.destroy()) + const clients = JSON.parse(json) as typeof hyprland.clients + clients + .filter(({ workspace }) => workspace.id === id) + .forEach(c => { + const x = c.at[0] - (hyprland.getMonitor(c.monitor)?.x || 0) + const y = c.at[1] - (hyprland.getMonitor(c.monitor)?.y || 0) + c.mapped && fixed.put(Window(c), scale(x), scale(y)) + }) + fixed.show_all() + } + + return Widget.Box({ + attribute: { id }, + tooltipText: `${id}`, + class_name: "workspace", + vpack: "center", + css: options.overview.scale.bind().as(v => ` + min-width: ${(v / 100) * size(id).w}px; + min-height: ${(v / 100) * size(id).h}px; + `), + setup(box) { + box.hook(options.overview.scale, update) + box.hook(hyprland, update, "notify::clients") + box.hook(hyprland.active.client, update) + box.hook(hyprland.active.workspace, () => { + box.toggleClassName("active", hyprland.active.workspace.id === id) + }) + }, + child: Widget.EventBox({ + expand: true, + on_primary_click: () => { + App.closeWindow("overview") + dispatch(`workspace ${id}`) + }, + setup: eventbox => { + eventbox.drag_dest_set(Gtk.DestDefaults.ALL, TARGET, Gdk.DragAction.COPY) + eventbox.connect("drag-data-received", (_w, _c, _x, _y, data) => { + const address = new TextDecoder().decode(data.get_data()) + dispatch(`movetoworkspacesilent ${id},address:${address}`) + }) + }, + child: fixed, + }), + }) +} diff --git a/overlays/asztal/widget/overview/overview.scss b/overlays/asztal/widget/overview/overview.scss new file mode 100644 index 0000000..c0c4b13 --- /dev/null +++ b/overlays/asztal/widget/overview/overview.scss @@ -0,0 +1,34 @@ +window#overview .overview { + @include floating-widget; + @include spacing; + + .workspace { + &.active>widget { + border-color: $primary-bg + } + + >widget { + @include widget; + border-radius: if($radius ==0, 0, $radius + $padding); + + &:hover { + background-color: $hover-bg; + } + + &:drop(active) { + border-color: $primary-bg; + } + } + } + + .client { + @include button; + border-radius: $radius; + margin: $padding; + + &.hidden { + @include hidden; + transition: 0; + } + } +} diff --git a/overlays/asztal/widget/powermenu/PowerMenu.ts b/overlays/asztal/widget/powermenu/PowerMenu.ts new file mode 100644 index 0000000..fe0a0e9 --- /dev/null +++ b/overlays/asztal/widget/powermenu/PowerMenu.ts @@ -0,0 +1,56 @@ +import PopupWindow from "widget/PopupWindow" +import powermenu, { type Action } from "service/powermenu" +import icons from "lib/icons" +import options from "options" +import type Gtk from "gi://Gtk?version=3.0" + +const { layout, labels } = options.powermenu + +const SysButton = (action: Action, label: string) => Widget.Button({ + on_clicked: () => powermenu.action(action), + child: Widget.Box({ + vertical: true, + class_name: "system-button", + children: [ + Widget.Icon(icons.powermenu[action]), + Widget.Label({ + label, + visible: labels.bind(), + }), + ], + }), +}) + +export default () => PopupWindow({ + name: "powermenu", + transition: "crossfade", + child: Widget.Box({ + class_name: "powermenu horizontal", + setup: self => self.hook(layout, () => { + self.toggleClassName("box", layout.value === "box") + self.toggleClassName("line", layout.value === "line") + }), + children: layout.bind().as(layout => { + switch (layout) { + case "line": return [ + SysButton("shutdown", "Shutdown"), + SysButton("logout", "Log Out"), + SysButton("reboot", "Reboot"), + SysButton("sleep", "Sleep"), + ] + case "box": return [ + Widget.Box( + { vertical: true }, + SysButton("shutdown", "Shutdown"), + SysButton("logout", "Log Out"), + ), + Widget.Box( + { vertical: true }, + SysButton("reboot", "Reboot"), + SysButton("sleep", "Sleep"), + ), + ] + } + }), + }), +}) diff --git a/overlays/asztal/widget/powermenu/Verification.ts b/overlays/asztal/widget/powermenu/Verification.ts new file mode 100644 index 0000000..e85c81a --- /dev/null +++ b/overlays/asztal/widget/powermenu/Verification.ts @@ -0,0 +1,47 @@ +import PopupWindow from "widget/PopupWindow" +import powermenu from "service/powermenu" + +export default () => PopupWindow({ + name: "verification", + transition: "crossfade", + child: Widget.Box({ + class_name: "verification", + vertical: true, + children: [ + Widget.Box({ + class_name: "text-box", + vertical: true, + children: [ + Widget.Label({ + class_name: "title", + label: powermenu.bind("title"), + }), + Widget.Label({ + class_name: "desc", + label: "Are you sure?", + }), + ], + }), + Widget.Box({ + class_name: "buttons horizontal", + vexpand: true, + vpack: "end", + homogeneous: true, + children: [ + Widget.Button({ + child: Widget.Label("No"), + on_clicked: () => App.toggleWindow("verification"), + setup: self => self.hook(App, (_, name: string, visible: boolean) => { + if (name === "verification" && visible) + self.grab_focus() + }), + }), + Widget.Button({ + child: Widget.Label("Yes"), + on_clicked: () => Utils.exec(powermenu.cmd), + }), + ], + }), + ], + }), +}) diff --git a/overlays/asztal/widget/powermenu/powermenu.scss b/overlays/asztal/widget/powermenu/powermenu.scss new file mode 100644 index 0000000..d5ce0de --- /dev/null +++ b/overlays/asztal/widget/powermenu/powermenu.scss @@ -0,0 +1,110 @@ +window#powermenu, +window#verification { + // the fraction has to be more than hyprland ignorealpha + background-color: rgba(0, 0, 0, .4); +} + +window#verification .verification { + @include floating-widget; + padding: $popover-padding * 1.5; + min-width: 300px; + min-height: 100px; + + .text-box { + margin-bottom: $spacing; + + .title { + font-size: 1.6em; + } + + .desc { + color: transparentize($fg, 0.1); + font-size: 1.1em; + } + } + + .buttons { + @include spacing; + margin-top: $padding; + + button { + @include button; + font-size: 1.5em; + padding: $padding; + } + } +} + +window#powermenu .powermenu { + @include floating-widget; + + &.line { + padding: $popover-padding * 1.5; + + button { + padding: $popover-padding; + } + + label { + margin-bottom: $spacing * -.5; + } + } + + &.box { + padding: $popover-padding * 2; + + button { + padding: $popover-padding * 1.5; + } + + label { + margin-bottom: $spacing * -1; + } + } + + button { + @include unset; + + image { + @include button; + border-radius: $radius + ($popover-padding * 1.4); + min-width: 1.7em; + min-height: 1.7em; + font-size: 4em; + } + + label, + image { + color: transparentize($fg, 0.1); + } + + label { + margin-top: $spacing * .3; + } + + &:hover { + image { + @include button-hover; + } + + label { + color: $fg; + } + } + + &:focus image { + @include button-focus; + } + + &:active image { + @include button-active; + } + + &:focus, + &:active { + label { + color: $primary-bg; + } + } + } +} diff --git a/overlays/asztal/widget/quicksettings/QuickSettings.ts b/overlays/asztal/widget/quicksettings/QuickSettings.ts new file mode 100644 index 0000000..2c0d6ac --- /dev/null +++ b/overlays/asztal/widget/quicksettings/QuickSettings.ts @@ -0,0 +1,84 @@ +import type Gtk from "gi://Gtk?version=3.0" +import { ProfileSelector, ProfileToggle } from "./widgets/PowerProfile" +import { Header } from "./widgets/Header" +import { Volume, Microhone, SinkSelector, AppMixer } from "./widgets/Volume" +import { Brightness } from "./widgets/Brightness" +import { NetworkToggle, WifiSelection } from "./widgets/Network" +import { BluetoothToggle, BluetoothDevices } from "./widgets/Bluetooth" +import { DND } from "./widgets/DND" +import { DarkModeToggle } from "./widgets/DarkMode" +import { MicMute } from "./widgets/MicMute" +import { Media } from "./widgets/Media" +import PopupWindow from "widget/PopupWindow" +import options from "options" + +const { bar, quicksettings } = options +const media = (await Service.import("mpris")).bind("players") +const layout = Utils.derive([bar.position, quicksettings.position], (bar, qs) => + `${bar}-${qs}` as const, +) + +const Row = ( + toggles: Array<() => Gtk.Widget> = [], + menus: Array<() => Gtk.Widget> = [], +) => Widget.Box({ + vertical: true, + children: [ + Widget.Box({ + homogeneous: true, + class_name: "row horizontal", + children: toggles.map(w => w()), + }), + ...menus.map(w => w()), + ], +}) + +const Settings = () => Widget.Box({ + vertical: true, + class_name: "quicksettings vertical", + css: quicksettings.width.bind().as(w => `min-width: ${w}px;`), + children: [ + Header(), + Widget.Box({ + class_name: "sliders-box vertical", + vertical: true, + children: [ + Row( + [Volume], + [SinkSelector, AppMixer], + ), + Microhone(), + Brightness(), + ], + }), + Row( + [NetworkToggle, BluetoothToggle], + [WifiSelection, BluetoothDevices], + ), + Row( + [ProfileToggle, DarkModeToggle], + [ProfileSelector], + ), + Row([MicMute, DND]), + Widget.Box({ + visible: media.as(l => l.length > 0), + child: Media(), + }), + ], +}) + +const QuickSettings = () => PopupWindow({ + name: "quicksettings", + exclusivity: "exclusive", + transition: bar.position.bind().as(pos => pos === "top" ? "slide_down" : "slide_up"), + layout: layout.value, + child: Settings(), +}) + +export function setupQuickSettings() { + App.addWindow(QuickSettings()) + layout.connect("changed", () => { + App.removeWindow("quicksettings") + App.addWindow(QuickSettings()) + }) +} diff --git a/overlays/asztal/widget/quicksettings/ToggleButton.ts b/overlays/asztal/widget/quicksettings/ToggleButton.ts new file mode 100644 index 0000000..62a2e67 --- /dev/null +++ b/overlays/asztal/widget/quicksettings/ToggleButton.ts @@ -0,0 +1,154 @@ +import { type Props as IconProps } from "types/widgets/icon" +import { type Props as LabelProps } from "types/widgets/label" +import type GObject from "gi://GObject?version=2.0" +import type Gtk from "gi://Gtk?version=3.0" +import icons from "lib/icons" + +export const opened = Variable("") +App.connect("window-toggled", (_, name: string, visible: boolean) => { + if (name === "quicksettings" && !visible) + Utils.timeout(500, () => opened.value = "") +}) + +export const Arrow = (name: string, activate?: false | (() => void)) => { + let deg = 0 + let iconOpened = false + const icon = Widget.Icon(icons.ui.arrow.right).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, + class_name: "arrow", + on_clicked: () => { + opened.value = opened.value === name ? "" : name + if (typeof activate === "function") + activate() + }, + }) +} + +type ArrowToggleButtonProps = { + name: string + icon: IconProps["icon"] + label: LabelProps["label"] + activate: () => void + deactivate: () => void + activateOnArrow?: boolean + connection: [GObject.Object, () => boolean] +} +export const ArrowToggleButton = ({ + name, + icon, + label, + activate, + deactivate, + activateOnArrow = true, + connection: [service, condition], +}: ArrowToggleButtonProps) => Widget.Box({ + class_name: "toggle-button", + setup: self => self.hook(service, () => { + self.toggleClassName("active", condition()) + }), + children: [ + Widget.Button({ + child: Widget.Box({ + hexpand: true, + children: [ + Widget.Icon({ + class_name: "icon", + icon, + }), + Widget.Label({ + class_name: "label", + max_width_chars: 10, + truncate: "end", + label, + }), + ], + }), + on_clicked: () => { + if (condition()) { + deactivate() + if (opened.value === name) + opened.value = "" + } else { + activate() + } + }, + }), + Arrow(name, activateOnArrow && activate), + ], +}) + +type MenuProps = { + name: string + icon: IconProps["icon"] + title: LabelProps["label"] + content: Gtk.Widget[] +} +export const Menu = ({ name, icon, title, content }: MenuProps) => Widget.Revealer({ + transition: "slide_down", + reveal_child: opened.bind().as(v => v === name), + child: Widget.Box({ + class_names: ["menu", name], + vertical: true, + children: [ + Widget.Box({ + class_name: "title-box", + children: [ + Widget.Icon({ + class_name: "icon", + icon, + }), + Widget.Label({ + class_name: "title", + truncate: "end", + label: title, + }), + ], + }), + Widget.Separator(), + Widget.Box({ + vertical: true, + class_name: "content vertical", + children: content, + }), + ], + }), +}) + +type SimpleToggleButtonProps = { + icon: IconProps["icon"] + label: LabelProps["label"] + toggle: () => void + connection: [GObject.Object, () => boolean] +} +export const SimpleToggleButton = ({ + icon, + label, + toggle, + connection: [service, condition], +}: SimpleToggleButtonProps) => Widget.Button({ + on_clicked: toggle, + class_name: "simple-toggle", + setup: self => self.hook(service, () => { + self.toggleClassName("active", condition()) + }), + child: Widget.Box([ + Widget.Icon({ icon }), + Widget.Label({ + max_width_chars: 10, + truncate: "end", + label, + }), + ]), +}) diff --git a/overlays/asztal/widget/quicksettings/quicksettings.scss b/overlays/asztal/widget/quicksettings/quicksettings.scss new file mode 100644 index 0000000..bd18ff1 --- /dev/null +++ b/overlays/asztal/widget/quicksettings/quicksettings.scss @@ -0,0 +1,177 @@ +window#quicksettings .quicksettings { + @include floating-widget; + @include spacing; + + padding: $popover-padding * 1.4; + + .avatar { + @include widget; + border-radius: $radius * 3; + } + + .header { + @include spacing(.5); + color: transparentize($fg, .15); + + button { + @include button; + padding: $padding; + + image { + font-size: 1.4em; + } + } + } + + .sliders-box { + @include widget; + padding: $padding; + + button { + @include button($flat: true); + padding: $padding * .5; + } + + .volume button.arrow:last-child { + margin-left: $spacing * .4; + } + + .volume, + .brightness { + padding: $padding * .5; + } + + scale { + @include slider; + margin: 0 ($spacing * .5); + + &.muted highlight { + background-image: none; + background-color: transparentize($fg, $amount: .2); + } + } + } + + .row { + @include spacing; + } + + .menu { + @include unset; + @include widget; + padding: $padding; + margin-top: $spacing; + + .icon { + margin: 0 ($spacing * .5); + margin-left: $spacing * .2; + } + + .title { + font-weight: bold; + } + + separator { + margin: ($radius * .5); + background-color: $border-color; + } + + button { + @include button($flat: true); + padding: ($padding * .5); + + image:first-child { + margin-right: $spacing * .5; + } + } + + .bluetooth-devices { + @include spacing(.5); + } + + switch { + @include switch; + } + } + + .sliders-box .menu { + margin: ($spacing * .5) 0; + + &.app-mixer { + .mixer-item { + padding: $padding * .5; + padding-left: 0; + padding-right: $padding * 2; + + scale { + @include slider($width: .5em); + } + + image { + font-size: 1.2em; + margin: 0 $padding; + } + } + } + } + + .toggle-button { + @include button; + font-weight: bold; + + image { + font-size: 1.3em; + } + + label { + margin-left: $spacing * .3; + } + + button { + @include button($flat: true); + + &:first-child { + padding: $padding * 1.2; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + + &:last-child { + padding: $padding * .5; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + } + + &.active { + background-color: $primary-bg; + + label, + image { + color: $primary-fg; + } + } + } + + .simple-toggle { + @include button; + font-weight: bold; + padding: $padding * 1.2; + + label { + margin-left: $spacing * .3; + } + + image { + font-size: 1.3em; + } + } + + .media { + @include spacing; + + .player { + @include media; + } + } +} diff --git a/overlays/asztal/widget/quicksettings/widgets/Bluetooth.ts b/overlays/asztal/widget/quicksettings/widgets/Bluetooth.ts new file mode 100644 index 0000000..649e654 --- /dev/null +++ b/overlays/asztal/widget/quicksettings/widgets/Bluetooth.ts @@ -0,0 +1,61 @@ +import { type BluetoothDevice } from "types/service/bluetooth" +import { Menu, ArrowToggleButton } from "../ToggleButton" +import icons from "lib/icons" + +const bluetooth = await Service.import("bluetooth") + +export const BluetoothToggle = () => ArrowToggleButton({ + name: "bluetooth", + icon: bluetooth.bind("enabled").as(p => icons.bluetooth[p ? "enabled" : "disabled"]), + label: Utils.watch("Disabled", bluetooth, () => { + if (!bluetooth.enabled) + return "Disabled" + + if (bluetooth.connected_devices.length === 1) + return bluetooth.connected_devices[0].alias + + return `${bluetooth.connected_devices.length} Connected` + }), + connection: [bluetooth, () => bluetooth.enabled], + deactivate: () => bluetooth.enabled = false, + activate: () => bluetooth.enabled = true, +}) + +const DeviceItem = (device: BluetoothDevice) => Widget.Box({ + children: [ + Widget.Icon(device.icon_name + "-symbolic"), + Widget.Label(device.name), + Widget.Label({ + label: `${device.battery_percentage}%`, + visible: device.bind("battery_percentage").as(p => p > 0), + }), + Widget.Box({ hexpand: true }), + Widget.Spinner({ + active: device.bind("connecting"), + visible: device.bind("connecting"), + }), + Widget.Switch({ + active: device.connected, + visible: device.bind("connecting").as(p => !p), + setup: self => self.on("notify::active", () => { + device.setConnection(self.active) + }), + }), + ], +}) + +export const BluetoothDevices = () => Menu({ + name: "bluetooth", + icon: icons.bluetooth.disabled, + title: "Bluetooth", + content: [ + Widget.Box({ + class_name: "bluetooth-devices", + hexpand: true, + vertical: true, + children: bluetooth.bind("devices").as(ds => ds + .filter(d => d.name) + .map(DeviceItem)), + }), + ], +}) diff --git a/overlays/asztal/widget/quicksettings/widgets/Brightness.ts b/overlays/asztal/widget/quicksettings/widgets/Brightness.ts new file mode 100644 index 0000000..a3ce565 --- /dev/null +++ b/overlays/asztal/widget/quicksettings/widgets/Brightness.ts @@ -0,0 +1,23 @@ +import icons from "lib/icons" +import brightness from "service/brightness" + +const BrightnessSlider = () => Widget.Slider({ + draw_value: false, + hexpand: true, + value: brightness.bind("screen"), + on_change: ({ value }) => brightness.screen = value, +}) + +export const Brightness = () => Widget.Box({ + class_name: "brightness", + children: [ + Widget.Button({ + vpack: "center", + child: Widget.Icon(icons.brightness.indicator), + on_clicked: () => brightness.screen = 0, + tooltip_text: brightness.bind("screen").as(v => + `Screen Brightness: ${Math.floor(v * 100)}%`), + }), + BrightnessSlider(), + ], +}) diff --git a/overlays/asztal/widget/quicksettings/widgets/DND.ts b/overlays/asztal/widget/quicksettings/widgets/DND.ts new file mode 100644 index 0000000..7fc1fd0 --- /dev/null +++ b/overlays/asztal/widget/quicksettings/widgets/DND.ts @@ -0,0 +1,12 @@ +import { SimpleToggleButton } from "../ToggleButton" +import icons from "lib/icons" + +const n = await Service.import("notifications") +const dnd = n.bind("dnd") + +export const DND = () => SimpleToggleButton({ + icon: dnd.as(dnd => icons.notifications[dnd ? "silent" : "noisy"]), + label: dnd.as(dnd => dnd ? "Silent" : "Noisy"), + toggle: () => n.dnd = !n.dnd, + connection: [n, () => n.dnd], +}) diff --git a/overlays/asztal/widget/quicksettings/widgets/DarkMode.ts b/overlays/asztal/widget/quicksettings/widgets/DarkMode.ts new file mode 100644 index 0000000..9ec94df --- /dev/null +++ b/overlays/asztal/widget/quicksettings/widgets/DarkMode.ts @@ -0,0 +1,12 @@ +import { SimpleToggleButton } from "../ToggleButton" +import icons from "lib/icons" +import options from "options" + +const { scheme } = options.theme + +export const DarkModeToggle = () => SimpleToggleButton({ + icon: scheme.bind().as(s => icons.color[s]), + label: scheme.bind().as(s => s === "dark" ? "Dark" : "Light"), + toggle: () => scheme.value = scheme.value === "dark" ? "light" : "dark", + connection: [scheme, () => scheme.value === "dark"], +}) diff --git a/overlays/asztal/widget/quicksettings/widgets/Header.ts b/overlays/asztal/widget/quicksettings/widgets/Header.ts new file mode 100644 index 0000000..72bada0 --- /dev/null +++ b/overlays/asztal/widget/quicksettings/widgets/Header.ts @@ -0,0 +1,63 @@ +import icons from "lib/icons" +import { uptime } from "lib/variables" +import options from "options" +import powermenu, { Action } from "service/powermenu" + +const battery = await Service.import("battery") +const { image, size } = options.quicksettings.avatar + +function up(up: number) { + const h = Math.floor(up / 60) + const m = Math.floor(up % 60) + return `${h}h ${m < 10 ? "0" + m : m}m` +} + +const Avatar = () => Widget.Box({ + class_name: "avatar", + css: Utils.merge([image.bind(), size.bind()], (img, size) => ` + min-width: ${size}px; + min-height: ${size}px; + background-image: url('${img}'); + background-size: cover; + `), +}) + +const SysButton = (action: Action) => Widget.Button({ + vpack: "center", + child: Widget.Icon(icons.powermenu[action]), + on_clicked: () => powermenu.action(action), +}) + +export const Header = () => Widget.Box( + { class_name: "header horizontal" }, + Avatar(), + Widget.Box({ + vertical: true, + vpack: "center", + children: [ + Widget.Box({ + visible: battery.bind("available"), + children: [ + Widget.Icon({ icon: battery.bind("icon_name") }), + Widget.Label({ label: battery.bind("percent").as(p => `${p}%`) }), + ], + }), + Widget.Box([ + Widget.Icon({ icon: icons.ui.time }), + Widget.Label({ label: uptime.bind().as(up) }), + ]), + ], + }), + Widget.Box({ hexpand: true }), + Widget.Button({ + vpack: "center", + child: Widget.Icon(icons.ui.settings), + on_clicked: () => { + App.closeWindow("quicksettings") + App.closeWindow("settings-dialog") + App.openWindow("settings-dialog") + }, + }), + SysButton("logout"), + SysButton("shutdown"), +) diff --git a/overlays/asztal/widget/quicksettings/widgets/Media.ts b/overlays/asztal/widget/quicksettings/widgets/Media.ts new file mode 100644 index 0000000..52254ea --- /dev/null +++ b/overlays/asztal/widget/quicksettings/widgets/Media.ts @@ -0,0 +1,153 @@ +import { type MprisPlayer } from "types/service/mpris" +import icons from "lib/icons" +import options from "options" +import { icon } from "lib/utils" + +const mpris = await Service.import("mpris") +const players = mpris.bind("players") +const { media } = options.quicksettings + +function lengthStr(length: number) { + const min = Math.floor(length / 60) + const sec = Math.floor(length % 60) + const sec0 = sec < 10 ? "0" : "" + return `${min}:${sec0}${sec}` +} + +const Player = (player: MprisPlayer) => { + const cover = Widget.Box({ + class_name: "cover", + vpack: "start", + css: Utils.merge([ + player.bind("cover_path"), + player.bind("track_cover_url"), + media.coverSize.bind(), + ], (path, url, size) => ` + min-width: ${size}px; + min-height: ${size}px; + background-image: url('${path || url}'); + `), + }) + + const title = Widget.Label({ + class_name: "title", + max_width_chars: 20, + truncate: "end", + hpack: "start", + label: player.bind("track_title"), + }) + + const artist = Widget.Label({ + class_name: "artist", + max_width_chars: 20, + truncate: "end", + hpack: "start", + label: player.bind("track_artists").as(a => a.join(", ")), + }) + + const positionSlider = Widget.Slider({ + class_name: "position", + draw_value: false, + on_change: ({ value }) => player.position = value * player.length, + setup: self => { + const update = () => { + const { length, position } = player + self.visible = length > 0 + self.value = length > 0 ? position / length : 0 + } + self.hook(player, update) + self.hook(player, update, "position") + self.poll(1000, update) + }, + }) + + const positionLabel = Widget.Label({ + class_name: "position", + hpack: "start", + setup: self => { + const update = (_: unknown, time?: number) => { + self.label = lengthStr(time || player.position) + self.visible = player.length > 0 + } + self.hook(player, update, "position") + self.poll(1000, update) + }, + }) + + const lengthLabel = Widget.Label({ + class_name: "length", + hpack: "end", + visible: player.bind("length").as(l => l > 0), + label: player.bind("length").as(lengthStr), + }) + + const playericon = Widget.Icon({ + class_name: "icon", + hexpand: true, + hpack: "end", + vpack: "start", + tooltip_text: player.identity || "", + icon: Utils.merge([player.bind("entry"), media.monochromeIcon.bind()], (e, s) => { + const name = `${e}${s ? "-symbolic" : ""}` + return icon(name, icons.fallback.audio) + }), + }) + + const playPause = Widget.Button({ + class_name: "play-pause", + on_clicked: () => player.playPause(), + visible: player.bind("can_play"), + child: Widget.Icon({ + icon: player.bind("play_back_status").as(s => { + switch (s) { + case "Playing": return icons.mpris.playing + case "Paused": + case "Stopped": return icons.mpris.stopped + } + }), + }), + }) + + const prev = Widget.Button({ + on_clicked: () => player.previous(), + visible: player.bind("can_go_prev"), + child: Widget.Icon(icons.mpris.prev), + }) + + const next = Widget.Button({ + on_clicked: () => player.next(), + visible: player.bind("can_go_next"), + child: Widget.Icon(icons.mpris.next), + }) + + return Widget.Box( + { class_name: "player", vexpand: false }, + cover, + Widget.Box( + { vertical: true }, + Widget.Box([ + title, + playericon, + ]), + artist, + Widget.Box({ vexpand: true }), + positionSlider, + Widget.CenterBox({ + class_name: "footer horizontal", + start_widget: positionLabel, + center_widget: Widget.Box([ + prev, + playPause, + next, + ]), + end_widget: lengthLabel, + }), + ), + ) +} + +export const Media = () => Widget.Box({ + vertical: true, + class_name: "media vertical", + children: players.as(p => p.map(Player)), +}) diff --git a/overlays/asztal/widget/quicksettings/widgets/MicMute.ts b/overlays/asztal/widget/quicksettings/widgets/MicMute.ts new file mode 100644 index 0000000..b6e9454 --- /dev/null +++ b/overlays/asztal/widget/quicksettings/widgets/MicMute.ts @@ -0,0 +1,18 @@ +import { SimpleToggleButton } from "../ToggleButton" +import icons from "lib/icons" +const { microphone } = await Service.import("audio") + +const icon = () => microphone.is_muted || microphone.stream?.is_muted + ? icons.audio.mic.muted + : icons.audio.mic.high + +const label = () => microphone.is_muted || microphone.stream?.is_muted + ? "Muted" + : "Unmuted" + +export const MicMute = () => SimpleToggleButton({ + icon: Utils.watch(icon(), microphone, icon), + label: Utils.watch(label(), microphone, label), + toggle: () => microphone.is_muted = !microphone.is_muted, + connection: [microphone, () => microphone?.is_muted || false], +}) diff --git a/overlays/asztal/widget/quicksettings/widgets/Network.ts b/overlays/asztal/widget/quicksettings/widgets/Network.ts new file mode 100644 index 0000000..eb14ab4 --- /dev/null +++ b/overlays/asztal/widget/quicksettings/widgets/Network.ts @@ -0,0 +1,61 @@ +import { Menu, ArrowToggleButton } from "../ToggleButton" +import icons from "lib/icons.js" +import { dependencies, sh } from "lib/utils" +import options from "options" +const { wifi } = await Service.import("network") + +export const NetworkToggle = () => ArrowToggleButton({ + name: "network", + icon: wifi.bind("icon_name"), + label: wifi.bind("ssid").as(ssid => ssid || "Not Connected"), + connection: [wifi, () => wifi.enabled], + deactivate: () => wifi.enabled = false, + activate: () => { + wifi.enabled = true + wifi.scan() + }, +}) + +export const WifiSelection = () => Menu({ + name: "network", + icon: wifi.bind("icon_name"), + title: "Wifi Selection", + content: [ + Widget.Box({ + vertical: true, + setup: self => self.hook(wifi, () => self.children = + wifi.access_points.map(ap => Widget.Button({ + on_clicked: () => { + if (dependencies("nmcli")) + Utils.execAsync(`nmcli device wifi connect ${ap.bssid}`) + }, + child: Widget.Box({ + children: [ + Widget.Icon(ap.iconName), + Widget.Label(ap.ssid || ""), + Widget.Icon({ + icon: icons.ui.tick, + hexpand: true, + hpack: "end", + setup: self => Utils.idle(() => { + if (!self.is_destroyed) + self.visible = ap.active + }), + }), + ], + }), + })), + ), + }), + Widget.Separator(), + Widget.Button({ + on_clicked: () => sh(options.quicksettings.networkSettings.value), + child: Widget.Box({ + children: [ + Widget.Icon(icons.ui.settings), + Widget.Label("Network"), + ], + }), + }), + ], +}) diff --git a/overlays/asztal/widget/quicksettings/widgets/PowerProfile.ts b/overlays/asztal/widget/quicksettings/widgets/PowerProfile.ts new file mode 100644 index 0000000..f566aaf --- /dev/null +++ b/overlays/asztal/widget/quicksettings/widgets/PowerProfile.ts @@ -0,0 +1,99 @@ +import { ArrowToggleButton, Menu } from "../ToggleButton" +import icons from "lib/icons" + +import asusctl from "service/asusctl" +const asusprof = asusctl.bind("profile") + +const AsusProfileToggle = () => ArrowToggleButton({ + name: "asusctl-profile", + icon: asusprof.as(p => icons.asusctl.profile[p]), + label: asusprof, + connection: [asusctl, () => asusctl.profile !== "Balanced"], + activate: () => asusctl.setProfile("Quiet"), + deactivate: () => asusctl.setProfile("Balanced"), + activateOnArrow: false, +}) + +const AsusProfileSelector = () => Menu({ + name: "asusctl-profile", + icon: asusprof.as(p => icons.asusctl.profile[p]), + title: "Profile Selector", + content: [ + Widget.Box({ + vertical: true, + hexpand: true, + children: [ + Widget.Box({ + vertical: true, + children: asusctl.profiles.map(prof => Widget.Button({ + on_clicked: () => asusctl.setProfile(prof), + child: Widget.Box({ + children: [ + Widget.Icon(icons.asusctl.profile[prof]), + Widget.Label(prof), + ], + }), + })), + }), + ], + }), + Widget.Separator(), + Widget.Button({ + on_clicked: () => Utils.execAsync("rog-control-center"), + child: Widget.Box({ + children: [ + Widget.Icon(icons.ui.settings), + Widget.Label("Rog Control Center"), + ], + }), + }), + ], +}) + + +const pp = await Service.import("powerprofiles") +const profile = pp.bind("active_profile") +const profiles = pp.profiles.map(p => p.Profile) + +const pretty = (str: string) => str + .split("-") + .map(str => `${str.at(0)?.toUpperCase()}${str.slice(1)}`) + .join(" ") + +const PowerProfileToggle = () => ArrowToggleButton({ + name: "asusctl-profile", + icon: profile.as(p => icons.powerprofile[p]), + label: profile.as(pretty), + connection: [pp, () => pp.active_profile !== profiles[1]], + activate: () => pp.active_profile = profiles[0], + deactivate: () => pp.active_profile = profiles[1], + activateOnArrow: false, +}) + +const PowerProfileSelector = () => Menu({ + name: "asusctl-profile", + icon: profile.as(p => icons.powerprofile[p]), + title: "Profile Selector", + content: [Widget.Box({ + vertical: true, + hexpand: true, + child: Widget.Box({ + vertical: true, + children: profiles.map(prof => Widget.Button({ + on_clicked: () => pp.active_profile = prof, + child: Widget.Box({ + children: [ + Widget.Icon(icons.powerprofile[prof]), + Widget.Label(pretty(prof)), + ], + }), + })), + }), + })], +}) + +export const ProfileToggle = asusctl.available + ? AsusProfileToggle : PowerProfileToggle + +export const ProfileSelector = asusctl.available + ? AsusProfileSelector : PowerProfileSelector diff --git a/overlays/asztal/widget/quicksettings/widgets/Volume.ts b/overlays/asztal/widget/quicksettings/widgets/Volume.ts new file mode 100644 index 0000000..ec9fedc --- /dev/null +++ b/overlays/asztal/widget/quicksettings/widgets/Volume.ts @@ -0,0 +1,150 @@ +import { type Stream } from "types/service/audio" +import { Arrow, Menu } from "../ToggleButton" +import { dependencies, icon, sh } from "lib/utils" +import icons from "lib/icons.js" +const audio = await Service.import("audio") + +type Type = "microphone" | "speaker" + +const VolumeIndicator = (type: Type = "speaker") => Widget.Button({ + vpack: "center", + on_clicked: () => audio[type].is_muted = !audio[type].is_muted, + child: Widget.Icon({ + icon: audio[type].bind("icon_name") + .as(i => icon(i || "", icons.audio.mic.high)), + tooltipText: audio[type].bind("volume") + .as(vol => `Volume: ${Math.floor(vol * 100)}%`), + }), +}) + +const VolumeSlider = (type: Type = "speaker") => Widget.Slider({ + hexpand: true, + draw_value: false, + on_change: ({ value, dragging }) => { + if (dragging) { + audio[type].volume = value + audio[type].is_muted = false + } + }, + value: audio[type].bind("volume"), + class_name: audio[type].bind("is_muted").as(m => m ? "muted" : ""), +}) + +export const Volume = () => Widget.Box({ + class_name: "volume", + children: [ + VolumeIndicator("speaker"), + VolumeSlider("speaker"), + Widget.Box({ + vpack: "center", + child: Arrow("sink-selector"), + }), + Widget.Box({ + vpack: "center", + child: Arrow("app-mixer"), + visible: audio.bind("apps").as(a => a.length > 0), + }), + ], +}) + +export const Microhone = () => Widget.Box({ + class_name: "slider horizontal", + visible: audio.bind("recorders").as(a => a.length > 0), + children: [ + VolumeIndicator("microphone"), + VolumeSlider("microphone"), + ], +}) + +const MixerItem = (stream: Stream) => Widget.Box( + { + hexpand: true, + class_name: "mixer-item horizontal", + }, + Widget.Icon({ + tooltip_text: stream.bind("name").as(n => n || ""), + icon: stream.bind("name").as(n => { + return Utils.lookUpIcon(n || "") + ? (n || "") + : icons.fallback.audio + }), + }), + Widget.Box( + { vertical: true }, + 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"), + on_change: ({ value }) => stream.volume = value, + }), + ), +) + +const SinkItem = (stream: Stream) => Widget.Button({ + hexpand: true, + on_clicked: () => audio.speaker = stream, + child: Widget.Box({ + children: [ + Widget.Icon({ + icon: icon(stream.icon_name || "", icons.fallback.audio), + tooltip_text: stream.icon_name || "", + }), + Widget.Label((stream.description || "").split(" ").slice(0, 4).join(" ")), + Widget.Icon({ + icon: icons.ui.tick, + hexpand: true, + hpack: "end", + visible: audio.speaker.bind("stream").as(s => s === stream.stream), + }), + ], + }), +}) + +const SettingsButton = () => Widget.Button({ + on_clicked: () => { + if (dependencies("pavucontrol")) + sh("pavucontrol") + }, + hexpand: true, + child: Widget.Box({ + children: [ + Widget.Icon(icons.ui.settings), + Widget.Label("Settings"), + ], + }), +}) + +export const AppMixer = () => Menu({ + name: "app-mixer", + icon: icons.audio.mixer, + title: "App Mixer", + content: [ + Widget.Box({ + vertical: true, + class_name: "vertical mixer-item-box", + children: audio.bind("apps").as(a => a.map(MixerItem)), + }), + Widget.Separator(), + SettingsButton(), + ], +}) + +export const SinkSelector = () => Menu({ + name: "sink-selector", + icon: icons.audio.type.headset, + title: "Sink Selector", + content: [ + Widget.Box({ + vertical: true, + children: audio.bind("speakers").as(a => a.map(SinkItem)), + }), + Widget.Separator(), + SettingsButton(), + ], +}) diff --git a/overlays/asztal/widget/settings/Group.ts b/overlays/asztal/widget/settings/Group.ts new file mode 100644 index 0000000..e9356e0 --- /dev/null +++ b/overlays/asztal/widget/settings/Group.ts @@ -0,0 +1,34 @@ +import icons from "lib/icons" +import Row from "./Row" + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export default (title: string, ...rows: ReturnType>[]) => Widget.Box( + { + class_name: "group", + vertical: true, + }, + Widget.Box([ + Widget.Label({ + hpack: "start", + vpack: "end", + class_name: "group-title", + label: title, + setup: w => Utils.idle(() => w.visible = !!title), + }), + title ? Widget.Button({ + hexpand: true, + hpack: "end", + child: Widget.Icon(icons.ui.refresh), + class_name: "group-reset", + sensitive: Utils.merge( + rows.map(({ attribute: { opt } }) => opt.bind().as(v => v !== opt.initial)), + (...values) => values.some(b => b), + ), + on_clicked: () => rows.forEach(row => row.attribute.opt.reset()), + }) : Widget.Box(), + ]), + Widget.Box({ + vertical: true, + children: rows, + }), +) diff --git a/overlays/asztal/widget/settings/Page.ts b/overlays/asztal/widget/settings/Page.ts new file mode 100644 index 0000000..220e560 --- /dev/null +++ b/overlays/asztal/widget/settings/Page.ts @@ -0,0 +1,19 @@ +import Group from "./Group" + +export default ( + name: string, + icon: string, + ...groups: ReturnType>[] +) => Widget.Box({ + class_name: "page", + attribute: { name, icon }, + child: Widget.Scrollable({ + css: "min-height: 300px;", + child: Widget.Box({ + class_name: "page-content", + vexpand: true, + vertical: true, + children: groups, + }), + }), +}) diff --git a/overlays/asztal/widget/settings/Row.ts b/overlays/asztal/widget/settings/Row.ts new file mode 100644 index 0000000..1e17096 --- /dev/null +++ b/overlays/asztal/widget/settings/Row.ts @@ -0,0 +1,55 @@ +import { Opt } from "lib/option" +import Setter from "./Setter" +import icons from "lib/icons" + +export type RowProps = { + opt: Opt + title: string + note?: string + type?: + | "number" + | "color" + | "float" + | "object" + | "string" + | "enum" + | "boolean" + | "img" + | "font" + enums?: string[] + max?: number + min?: number +} + +export default (props: RowProps) => Widget.Box( + { + attribute: { opt: props.opt }, + class_name: "row", + tooltip_text: props.note ? `note: ${props.note}` : "", + }, + Widget.Box( + { vertical: true, vpack: "center" }, + Widget.Label({ + xalign: 0, + class_name: "row-title", + label: props.title, + }), + Widget.Label({ + xalign: 0, + class_name: "id", + label: props.opt.id, + }), + ), + Widget.Box({ hexpand: true }), + Widget.Box( + { vpack: "center" }, + Setter(props), + ), + Widget.Button({ + vpack: "center", + class_name: "reset", + child: Widget.Icon(icons.ui.refresh), + on_clicked: () => props.opt.reset(), + sensitive: props.opt.bind().as(v => v !== props.opt.initial), + }), +) diff --git a/overlays/asztal/widget/settings/Setter.ts b/overlays/asztal/widget/settings/Setter.ts new file mode 100644 index 0000000..7e455c9 --- /dev/null +++ b/overlays/asztal/widget/settings/Setter.ts @@ -0,0 +1,93 @@ +import { type RowProps } from "./Row" +import { Opt } from "lib/option" +import icons from "lib/icons" +import Gdk from "gi://Gdk" + +function EnumSetter(opt: Opt, values: string[]) { + const lbl = Widget.Label({ label: opt.bind().as(v => `${v}`) }) + const step = (dir: 1 | -1) => { + const i = values.findIndex(i => i === lbl.label) + opt.setValue(dir > 0 + ? i + dir > values.length - 1 ? values[0] : values[i + dir] + : i + dir < 0 ? values[values.length - 1] : values[i + dir], + ) + } + const next = Widget.Button({ + child: Widget.Icon(icons.ui.arrow.right), + on_clicked: () => step(+1), + }) + const prev = Widget.Button({ + child: Widget.Icon(icons.ui.arrow.left), + on_clicked: () => step(-1), + }) + return Widget.Box({ + class_name: "enum-setter", + children: [lbl, prev, next], + }) +} + +export default function Setter({ + opt, + type = typeof opt.value as RowProps["type"], + enums, + max = 1000, + min = 0, +}: RowProps) { + switch (type) { + case "number": return Widget.SpinButton({ + setup(self) { + self.set_range(min, max) + self.set_increments(1, 5) + self.on("value-changed", () => opt.value = self.value as T) + self.hook(opt, () => self.value = opt.value as number) + }, + }) + + case "float": + case "object": return Widget.Entry({ + on_accept: self => opt.value = JSON.parse(self.text || ""), + setup: self => self.hook(opt, () => self.text = JSON.stringify(opt.value)), + }) + + case "string": return Widget.Entry({ + on_accept: self => opt.value = self.text as T, + setup: self => self.hook(opt, () => self.text = opt.value as string), + }) + + case "enum": return EnumSetter(opt as unknown as Opt, enums!) + case "boolean": return Widget.Switch() + .on("notify::active", self => opt.value = self.active as T) + .hook(opt, self => self.active = opt.value as boolean) + + case "img": return Widget.FileChooserButton({ + on_file_set: ({ uri }) => { opt.value = uri!.replace("file://", "") as T }, + }) + + case "font": return Widget.FontButton({ + show_size: false, + use_size: false, + setup: self => self + .hook(opt, () => self.font = opt.value as string) + .on("font-set", ({ font }) => opt.value = font! + .split(" ").slice(0, -1).join(" ") as T), + }) + + case "color": return Widget.ColorButton() + .hook(opt, self => { + const rgba = new Gdk.RGBA() + rgba.parse(opt.value as string) + self.rgba = rgba + }) + .on("color-set", ({ rgba: { red, green, blue } }) => { + const hex = (n: number) => { + const c = Math.floor(255 * n).toString(16) + return c.length === 1 ? `0${c}` : c + } + opt.value = `#${hex(red)}${hex(green)}${hex(blue)}` as T + }) + + default: return Widget.Label({ + label: `no setter with type ${type}`, + }) + } +} diff --git a/overlays/asztal/widget/settings/SettingsDialog.ts b/overlays/asztal/widget/settings/SettingsDialog.ts new file mode 100644 index 0000000..be0c35e --- /dev/null +++ b/overlays/asztal/widget/settings/SettingsDialog.ts @@ -0,0 +1,63 @@ +import RegularWindow from "widget/RegularWindow" +import layout from "./layout" +import icons from "lib/icons" +import options from "options" + +const current = Variable(layout[0].attribute.name) + +const Header = () => Widget.CenterBox({ + class_name: "header", + start_widget: Widget.Button({ + class_name: "reset", + on_clicked: options.reset, + hpack: "start", + vpack: "start", + child: Widget.Icon(icons.ui.refresh), + tooltip_text: "Reset", + }), + center_widget: Widget.Box({ + class_name: "pager horizontal", + children: layout.map(({ attribute: { name, icon } }) => Widget.Button({ + xalign: 0, + class_name: current.bind().as(v => `${v === name ? "active" : ""}`), + on_clicked: () => current.value = name, + child: Widget.Box([ + Widget.Icon(icon), + Widget.Label(name), + ]), + })), + }), + end_widget: Widget.Button({ + class_name: "close", + hpack: "end", + vpack: "start", + child: Widget.Icon(icons.ui.close), + on_clicked: () => App.closeWindow("settings-dialog"), + }), +}) + +const PagesStack = () => Widget.Stack({ + transition: "slide_left_right", + children: layout.reduce((obj, page) => ({ ...obj, [page.attribute.name]: page }), {}), + shown: current.bind() as never, +}) + +export default () => RegularWindow({ + name: "settings-dialog", + class_name: "settings-dialog", + title: "Settings", + setup(win) { + win.on("delete-event", () => { + win.hide() + return true + }) + win.set_default_size(500, 600) + }, + child: Widget.Box({ + vertical: true, + children: [ + Header(), + PagesStack(), + ], + }), +}) diff --git a/overlays/asztal/widget/settings/Wallpaper.ts b/overlays/asztal/widget/settings/Wallpaper.ts new file mode 100644 index 0000000..998f3b7 --- /dev/null +++ b/overlays/asztal/widget/settings/Wallpaper.ts @@ -0,0 +1,31 @@ +import wallpaper from "service/wallpaper" + +export default () => Widget.Box( + { class_name: "row wallpaper" }, + Widget.Box( + { vertical: true }, + Widget.Label({ + xalign: 0, + class_name: "row-title", + label: "Wallpaper", + vpack: "start", + }), + Widget.Button({ + on_clicked: wallpaper.random, + label: "Random", + }), + Widget.FileChooserButton({ + on_file_set: ({ uri }) => wallpaper.set(uri!.replace("file://", "")), + }), + ), + Widget.Box({ hexpand: true }), + Widget.Box({ + class_name: "preview", + css: wallpaper.bind("wallpaper").as(wp => ` + min-height: 120px; + min-width: 200px; + background-image: url('${wp}'); + background-size: cover; + `), + }), +) diff --git a/overlays/asztal/widget/settings/layout.ts b/overlays/asztal/widget/settings/layout.ts new file mode 100644 index 0000000..2b45810 --- /dev/null +++ b/overlays/asztal/widget/settings/layout.ts @@ -0,0 +1,147 @@ +/* eslint-disable max-len */ +import Row from "./Row" +import Group from "./Group" +import Page from "./Page" +import Wallpaper from "./Wallpaper" +import options from "options" +import icons from "lib/icons" + +const { + autotheme: at, + font, + theme, + bar: b, + launcher: l, + overview: ov, + powermenu: pm, + quicksettings: qs, + osd, + hyprland: h, +} = options + +const { + dark, + light, + blur, + scheme, + padding, + spacing, + radius, + shadows, + widget, + border, +} = theme + +export default [ + Page("Theme", icons.ui.themes, + Group("", + Wallpaper() as ReturnType, + Row({ opt: at, title: "Auto Generate Color Scheme" }), + Row({ opt: scheme, title: "Color Scheme", type: "enum", enums: ["dark", "light"] }), + ), + Group("Dark Colors", + Row({ opt: dark.bg, title: "Background", type: "color" }), + Row({ opt: dark.fg, title: "Foreground", type: "color" }), + Row({ opt: dark.primary.bg, title: "Primary", type: "color" }), + Row({ opt: dark.primary.fg, title: "On Primary", type: "color" }), + Row({ opt: dark.error.bg, title: "Error", type: "color" }), + Row({ opt: dark.error.fg, title: "On Error", type: "color" }), + Row({ opt: dark.widget, title: "Widget", type: "color" }), + Row({ opt: dark.border, title: "Border", type: "color" }), + ), + Group("Light Colors", + Row({ opt: light.bg, title: "Background", type: "color" }), + Row({ opt: light.fg, title: "Foreground", type: "color" }), + Row({ opt: light.primary.bg, title: "Primary", type: "color" }), + Row({ opt: light.primary.fg, title: "On Primary", type: "color" }), + Row({ opt: light.error.bg, title: "Error", type: "color" }), + Row({ opt: light.error.fg, title: "On Error", type: "color" }), + Row({ opt: light.widget, title: "Widget", type: "color" }), + Row({ opt: light.border, title: "Border", type: "color" }), + ), + Group("Theme", + Row({ opt: shadows, title: "Shadows" }), + Row({ opt: widget.opacity, title: "Widget Opacity", max: 100 }), + Row({ opt: border.opacity, title: "Border Opacity", max: 100 }), + Row({ opt: border.width, title: "Border Width" }), + Row({ opt: blur, title: "Blur", note: "0 to disable", max: 70 }), + ), + Group("UI", + Row({ opt: padding, title: "Padding" }), + Row({ opt: spacing, title: "Spacing" }), + Row({ opt: radius, title: "Roundness" }), + Row({ opt: font.size, title: "Font Size" }), + Row({ opt: font.name, title: "Font Name", type: "font" }), + ), + ), + Page("Bar", icons.ui.toolbars, + Group("General", + Row({ opt: b.flatButtons, title: "Flat Buttons" }), + Row({ opt: b.position, title: "Position", type: "enum", enums: ["top", "bottom"] }), + Row({ opt: b.corners, title: "Corners" }), + ), + Group("Launcher", + Row({ opt: b.launcher.icon.icon, title: "Icon" }), + Row({ opt: b.launcher.icon.colored, title: "Colored Icon" }), + Row({ opt: b.launcher.label.label, title: "Label" }), + Row({ opt: b.launcher.label.colored, title: "Colored Label" }), + ), + Group("Workspaces", + Row({ opt: b.workspaces.workspaces, title: "Number of Workspaces", note: "0 to make it dynamic" }), + ), + Group("Taskbar", + Row({ opt: b.taskbar.iconSize, title: "Icon Size" }), + Row({ opt: b.taskbar.monochrome, title: "Monochrome" }), + Row({ opt: b.taskbar.exclusive, title: "Exclusive to workspaces" }), + ), + Group("Date", + Row({ opt: b.date.format, title: "Date Format" }), + ), + Group("Media", + Row({ opt: b.media.monochrome, title: "Monochrome" }), + Row({ opt: b.media.preferred, title: "Preferred Player" }), + Row({ opt: b.media.direction, title: "Slide Direction", type: "enum", enums: ["left", "right"] }), + Row({ opt: b.media.format, title: "Format of the Label" }), + Row({ opt: b.media.length, title: "Max Length of Label" }), + ), + Group("Battery", + Row({ opt: b.battery.bar, title: "Style", type: "enum", enums: ["hidden", "regular", "whole"] }), + Row({ opt: b.battery.blocks, title: "Number of Blocks" }), + Row({ opt: b.battery.width, title: "Width of Bar" }), + Row({ opt: b.battery.charging, title: "Charging Color", type: "color" }), + ), + Group("Powermenu", + Row({ opt: b.powermenu.monochrome, title: "Monochrome" }), + ), + ), + Page("General", icons.ui.settings, + Group("Hyprland", + Row({ opt: h.gapsWhenOnly, title: "Gaps When Only" }), + ), + Group("Launcher", + Row({ opt: l.width, title: "Width" }), + Row({ opt: l.apps.iconSize, title: "Icon Size" }), + Row({ opt: l.apps.max, title: "Max Items" }), + ), + Group("Overview", + Row({ opt: ov.scale, title: "Scale", max: 100 }), + Row({ opt: ov.workspaces, title: "Workspaces", max: 11, note: "set this to 0 to make it dynamic" }), + Row({ opt: ov.monochromeIcon, title: "Monochrome Icons" }), + ), + Group("Powermenu", + Row({ opt: pm.layout, title: "Layout", type: "enum", enums: ["box", "line"] }), + Row({ opt: pm.labels, title: "Show Labels" }), + ), + Group("Quicksettings", + Row({ opt: qs.avatar.image, title: "Avatar", type: "img" }), + Row({ opt: qs.avatar.size, title: "Avatar Size" }), + Row({ opt: qs.media.monochromeIcon, title: "Media Monochrome Icons" }), + Row({ opt: qs.media.coverSize, title: "Media Cover Art Size" }), + ), + Group("On Screen Indicator", + Row({ opt: osd.progress.vertical, title: "Vertical" }), + Row({ opt: osd.progress.pack.h, title: "Horizontal Alignment", type: "enum", enums: ["start", "center", "end"] }), + Row({ opt: osd.progress.pack.v, title: "Vertical Alignment", type: "enum", enums: ["start", "center", "end"] }), + ), + ), +] as const diff --git a/overlays/asztal/widget/settings/settingsdialog.scss b/overlays/asztal/widget/settings/settingsdialog.scss new file mode 100644 index 0000000..b8c9820 --- /dev/null +++ b/overlays/asztal/widget/settings/settingsdialog.scss @@ -0,0 +1,144 @@ +window.settings-dialog { + background-color: $bg; + color: $fg; + + .header { + .pager { + @include spacing(.5); + } + + padding: $padding; + + button { + @include button; + font-weight: bold; + padding: $padding*.5 $padding; + + box { + @include spacing($spacing: .3em); + } + } + + button.close { + padding: $padding * .5; + } + + button.reset { + @include button($flat: true); + padding: $padding*.5; + } + } + + .page { + @include scrollable($top: true); + + .page-content { + padding: $padding*2; + padding-top: 0; + } + } + + .group { + .group-title { + color: $primary-bg; + margin-bottom: $spacing*.5; + } + + .group-reset { + @include button($flat: true); + margin: $spacing * .5; + padding: $padding * .5; + + &:disabled { + color: transparent; + } + } + + &:not(:first-child) { + margin-top: $spacing; + } + } + + .row { + background-color: $widget-bg; + padding: $padding; + border: $border; + border-top: none; + + &:first-child { + border-radius: $radius $radius 0 0; + border: $border; + } + + &:last-child { + border-radius: 0 0 $radius $radius; + } + + &:first-child:last-child { + border-radius: $radius; + border: $border; + } + + button.reset { + margin-left: $spacing; + } + + label.id, + label.note { + color: transparentize($fg, .4) + } + + entry, + button { + @include button; + padding: $padding; + } + + switch { + @include switch; + } + + spinbutton { + @include unset; + + entry { + border-radius: $radius 0 0 $radius; + } + + button { + border-radius: 0; + } + + button:last-child { + border-radius: 0 $radius $radius 0; + } + } + + .enum-setter { + label { + background-color: $widget-bg; + border: $border; + padding: 0 $padding; + border-radius: $radius 0 0 $radius; + } + + button { + border-radius: 0; + } + + button:last-child { + border-radius: 0 $radius $radius 0; + } + } + + &.wallpaper { + button { + margin-top: $spacing * .5; + } + + .preview { + border-radius: $radius; + } + } + } +}