From c86fb8b6d3c850fdd2adeb048cbe301f6298fccc Mon Sep 17 00:00:00 2001 From: Amneesh Singh Date: Sat, 1 Jun 2024 17:59:52 +0530 Subject: [PATCH] home/natto/ags: init Signed-off-by: Amneesh Singh --- .gitignore | 1 + flake.lock | 97 +++++++--- flake.nix | 15 +- home/default.nix | 2 +- home/natto/ags/README.md | 15 ++ home/natto/ags/config.js | 31 +++ home/natto/ags/constants.js | 6 + home/natto/ags/default.nix | 37 ++++ home/natto/ags/style.scss | 5 + home/natto/ags/styles/bar.scss | 40 ++++ home/natto/ags/styles/calendar.scss | 8 + home/natto/ags/styles/global.scss | 57 ++++++ home/natto/ags/styles/music-box.scss | 79 ++++++++ home/natto/ags/styles/settings.scss | 112 +++++++++++ home/natto/ags/tsconfig.json | 18 ++ home/natto/ags/utils/music.js | 42 ++++ home/natto/ags/utils/popup.js | 41 ++++ home/natto/ags/utils/text.js | 5 + home/natto/ags/windows/bar/bluetooth.js | 19 ++ home/natto/ags/windows/bar/hyprland.js | 38 ++++ home/natto/ags/windows/bar/index.js | 56 ++++++ home/natto/ags/windows/bar/music.js | 65 +++++++ home/natto/ags/windows/bar/network.js | 40 ++++ home/natto/ags/windows/bar/settings.js | 8 + home/natto/ags/windows/bar/time.js | 42 ++++ home/natto/ags/windows/bar/tray.js | 17 ++ home/natto/ags/windows/calendar.js | 37 ++++ home/natto/ags/windows/music-box/index.js | 183 ++++++++++++++++++ .../ags/windows/music-box/music-controls.js | 47 +++++ home/natto/ags/windows/settings/audio.js | 69 +++++++ home/natto/ags/windows/settings/backlight.js | 48 +++++ home/natto/ags/windows/settings/index.js | 72 +++++++ home/natto/ags/windows/settings/metrics.js | 104 ++++++++++ home/natto/ags/windows/settings/power-menu.js | 94 +++++++++ .../natto/ags/windows/settings/temperature.js | 62 ++++++ home/natto/ags/windows/settings/weather.js | 78 ++++++++ home/natto/wayland.nix | 1 + 37 files changed, 1658 insertions(+), 33 deletions(-) create mode 100644 home/natto/ags/README.md create mode 100644 home/natto/ags/config.js create mode 100644 home/natto/ags/constants.js create mode 100644 home/natto/ags/default.nix create mode 100644 home/natto/ags/style.scss create mode 100644 home/natto/ags/styles/bar.scss create mode 100644 home/natto/ags/styles/calendar.scss create mode 100644 home/natto/ags/styles/global.scss create mode 100644 home/natto/ags/styles/music-box.scss create mode 100644 home/natto/ags/styles/settings.scss create mode 100644 home/natto/ags/tsconfig.json create mode 100644 home/natto/ags/utils/music.js create mode 100644 home/natto/ags/utils/popup.js create mode 100644 home/natto/ags/utils/text.js create mode 100644 home/natto/ags/windows/bar/bluetooth.js create mode 100644 home/natto/ags/windows/bar/hyprland.js create mode 100644 home/natto/ags/windows/bar/index.js create mode 100644 home/natto/ags/windows/bar/music.js create mode 100644 home/natto/ags/windows/bar/network.js create mode 100644 home/natto/ags/windows/bar/settings.js create mode 100644 home/natto/ags/windows/bar/time.js create mode 100644 home/natto/ags/windows/bar/tray.js create mode 100644 home/natto/ags/windows/calendar.js create mode 100644 home/natto/ags/windows/music-box/index.js create mode 100644 home/natto/ags/windows/music-box/music-controls.js create mode 100644 home/natto/ags/windows/settings/audio.js create mode 100644 home/natto/ags/windows/settings/backlight.js create mode 100644 home/natto/ags/windows/settings/index.js create mode 100644 home/natto/ags/windows/settings/metrics.js create mode 100644 home/natto/ags/windows/settings/power-menu.js create mode 100644 home/natto/ags/windows/settings/temperature.js create mode 100644 home/natto/ags/windows/settings/weather.js diff --git a/.gitignore b/.gitignore index 59c1aa9..f1e4991 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ *~ *.html result +.sass-cache diff --git a/flake.lock b/flake.lock index 36a52f8..537fe1e 100644 --- a/flake.lock +++ b/flake.lock @@ -21,6 +21,24 @@ "type": "github" } }, + "ags": { + "inputs": { + "nixpkgs": "nixpkgs_2" + }, + "locked": { + "lastModified": 1715703984, + "narHash": "sha256-0BZkMui6aCqswMCouvp0G90tAxDOxVnxTvG6TDZsDaI=", + "owner": "Aylur", + "repo": "ags", + "rev": "11150225e62462bcd431d1e55185e810190a730a", + "type": "github" + }, + "original": { + "owner": "Aylur", + "repo": "ags", + "type": "github" + } + }, "blobs": { "flake": false, "locked": { @@ -41,7 +59,7 @@ "inputs": { "flake-compat": "flake-compat", "flake-utils": "flake-utils_2", - "nixpkgs": "nixpkgs_3", + "nixpkgs": "nixpkgs_4", "rust-overlay": "rust-overlay" }, "locked": { @@ -83,7 +101,7 @@ "emacs-overlay": { "inputs": { "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs_2", + "nixpkgs": "nixpkgs_3", "nixpkgs-stable": "nixpkgs-stable" }, "locked": { @@ -103,7 +121,7 @@ "filehost": { "inputs": { "cargo2nix": "cargo2nix", - "nixpkgs": "nixpkgs_4", + "nixpkgs": "nixpkgs_5", "rust-overlay": "rust-overlay_2", "utils": "utils" }, @@ -430,7 +448,7 @@ "hyprcursor": "hyprcursor", "hyprlang": "hyprlang", "hyprwayland-scanner": "hyprwayland-scanner", - "nixpkgs": "nixpkgs_6", + "nixpkgs": "nixpkgs_7", "systems": "systems_3", "xdph": "xdph" }, @@ -452,7 +470,7 @@ }, "hyprland-contrib": { "inputs": { - "nixpkgs": "nixpkgs_7" + "nixpkgs": "nixpkgs_8" }, "locked": { "lastModified": 1716228712, @@ -608,7 +626,7 @@ "nix-gaming": { "inputs": { "flake-parts": "flake-parts_2", - "nixpkgs": "nixpkgs_8" + "nixpkgs": "nixpkgs_9" }, "locked": { "lastModified": 1716686274, @@ -681,6 +699,22 @@ } }, "nixpkgs_10": { + "locked": { + "lastModified": 1716715802, + "narHash": "sha256-usk0vE7VlxPX8jOavrtpOqphdfqEQpf9lgedlY/r66c=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "e2dd4e18cc1c7314e24154331bae07df76eb582f", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_11": { "locked": { "lastModified": 1716588411, "narHash": "sha256-CdAZ3o459+1mAgILcdJfMBQAwUXupVe2cVTknvxs5kQ=", @@ -697,6 +731,22 @@ } }, "nixpkgs_2": { + "locked": { + "lastModified": 1708475490, + "narHash": "sha256-g1v0TsWBQPX97ziznfJdWhgMyMGtoBFs102xSYO4syU=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "0e74ca98a74bc7270d28838369593635a5db3260", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_3": { "locked": { "lastModified": 1716509168, "narHash": "sha256-4zSIhSRRIoEBwjbPm3YiGtbd8HDWzFxJjw5DYSDy1n8=", @@ -712,7 +762,7 @@ "type": "github" } }, - "nixpkgs_3": { + "nixpkgs_4": { "locked": { "lastModified": 1654275867, "narHash": "sha256-pt14ZE4jVPGvfB2NynGsl34pgXfOqum5YJNpDK4+b9E=", @@ -728,7 +778,7 @@ "type": "github" } }, - "nixpkgs_4": { + "nixpkgs_5": { "locked": { "lastModified": 1667085444, "narHash": "sha256-1SAlbifAAb+u8n52DUk6mB5oWv95o0qwRMHOMH3bS5g=", @@ -744,7 +794,7 @@ "type": "github" } }, - "nixpkgs_5": { + "nixpkgs_6": { "locked": { "lastModified": 1665296151, "narHash": "sha256-uOB0oxqxN9K7XGF1hcnY+PQnlQJ+3bP2vCn/+Ru/bbc=", @@ -760,7 +810,7 @@ "type": "github" } }, - "nixpkgs_6": { + "nixpkgs_7": { "locked": { "lastModified": 1716330097, "narHash": "sha256-8BO3B7e3BiyIDsaKA0tY8O88rClYRTjvAp66y+VBUeU=", @@ -776,7 +826,7 @@ "type": "github" } }, - "nixpkgs_7": { + "nixpkgs_8": { "locked": { "lastModified": 1712163089, "narHash": "sha256-Um+8kTIrC19vD4/lUCN9/cU9kcOsD1O1m+axJqQPyMM=", @@ -792,7 +842,7 @@ "type": "github" } }, - "nixpkgs_8": { + "nixpkgs_9": { "locked": { "lastModified": 1716619601, "narHash": "sha256-9dUxZf8MOqJH3vjbhrz7LH4qTcnRsPSBU1Q50T7q/X8=", @@ -808,29 +858,13 @@ "type": "github" } }, - "nixpkgs_9": { - "locked": { - "lastModified": 1716715802, - "narHash": "sha256-usk0vE7VlxPX8jOavrtpOqphdfqEQpf9lgedlY/r66c=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "e2dd4e18cc1c7314e24154331bae07df76eb582f", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixpkgs-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, "nvim-overlay": { "inputs": { "flake-compat": "flake-compat_3", "flake-parts": "flake-parts_3", "hercules-ci-effects": "hercules-ci-effects", "neovim-src": "neovim-src", - "nixpkgs": "nixpkgs_10", + "nixpkgs": "nixpkgs_11", "pre-commit-hooks": "pre-commit-hooks" }, "locked": { @@ -877,6 +911,7 @@ "root": { "inputs": { "agenix": "agenix", + "ags": "ags", "emacs-overlay": "emacs-overlay", "filehost": "filehost", "flake-parts": "flake-parts", @@ -886,7 +921,7 @@ "mailserver": "mailserver", "nbfc": "nbfc", "nix-gaming": "nix-gaming", - "nixpkgs": "nixpkgs_9", + "nixpkgs": "nixpkgs_10", "nvim-overlay": "nvim-overlay", "stable": "stable" } @@ -921,7 +956,7 @@ "rust-overlay_2": { "inputs": { "flake-utils": "flake-utils_3", - "nixpkgs": "nixpkgs_5" + "nixpkgs": "nixpkgs_6" }, "locked": { "lastModified": 1667011705, diff --git a/flake.nix b/flake.nix index a6676cf..813d1ae 100644 --- a/flake.nix +++ b/flake.nix @@ -3,38 +3,51 @@ inputs = { nixpkgs.url = github:nixos/nixpkgs/nixpkgs-unstable; + stable.url = github:nixos/nixpkgs/release-23.11; + flake-parts.url = github:hercules-ci/flake-parts; + home-manager = { url = github:nix-community/home-manager; inputs.nixpkgs.follows = "nixpkgs"; }; + mailserver = { url = gitlab:simple-nixos-mailserver/nixos-mailserver; inputs.nixpkgs.follows = "nixpkgs"; }; + filehost = { url = github:natto1784/simpler-filehost; - # inputs.nixpkgs.follows = "nixpkgs"; }; + nix-gaming.url = github:fufexan/nix-gaming; + nbfc = { url = github:nbfc-linux/nbfc-linux; inputs.nixpkgs.follows = "nixpkgs"; }; + emacs-overlay.url = github:nix-community/emacs-overlay; + nvim-overlay = { url = github:nix-community/neovim-nightly-overlay; }; + hyprland = { type = "git"; url = "https://github.com/hyprwm/Hyprland"; submodules = true; }; + hyprland-contrib = { url = github:hyprwm/contrib; }; + agenix.url = github:ryantm/agenix; + + ags.url = github:Aylur/ags; }; outputs = inputs@{ self, ... }: diff --git a/home/default.nix b/home/default.nix index 17a038b..0a80b5d 100644 --- a/home/default.nix +++ b/home/default.nix @@ -23,8 +23,8 @@ in nattoModules = [ ./natto ./common/laptop.nix - inputs.hyprland.homeManagerModules.default inputs.agenix.homeManagerModules.default + inputs.hyprland.homeManagerModules.default ] ++ common; in { diff --git a/home/natto/ags/README.md b/home/natto/ags/README.md new file mode 100644 index 0000000..e311392 --- /dev/null +++ b/home/natto/ags/README.md @@ -0,0 +1,15 @@ + +# Starter Config + +if suggestions don't work, first make sure +you have TypeScript LSP working in your editor + +if you do not want typechecking only suggestions + +```json +// tsconfig.json +"checkJs": false +``` + +types are symlinked to: +/nix/store/4rpg1hbvvfb8wpxf1a6ljbm390wfcwcd-ags-1.8.2/share/com.github.Aylur.ags/types diff --git a/home/natto/ags/config.js b/home/natto/ags/config.js new file mode 100644 index 0000000..4fff2f0 --- /dev/null +++ b/home/natto/ags/config.js @@ -0,0 +1,31 @@ +import Bar from "./windows/bar/index.js"; +import Settings from "./windows/settings/index.js"; +import MusicBox from "./windows/music-box/index.js"; +import Calendar from "./windows/calendar.js"; + +const configDir = App.configDir; + +const scssStyle = `${configDir}/style.scss`; +const cssStyle = `${configDir}/style.css`; + +const compileSass = () => { + Utils.exec(`scss ${scssStyle} ${cssStyle}`); + console.log("sass compiled to css"); +}; + +compileSass(); + +Utils.monitorFile(`${configDir}/styles`, () => { + console.log("change detected in style"); + compileSass(); + App.resetCss(); + App.applyCss(cssStyle); + console.log("new style applied"); +}); + +App.config({ + style: "./style.css", + windows: [Bar(), MusicBox(), Settings(), Calendar()], +}); + +export {}; diff --git a/home/natto/ags/constants.js b/home/natto/ags/constants.js new file mode 100644 index 0000000..eb015ab --- /dev/null +++ b/home/natto/ags/constants.js @@ -0,0 +1,6 @@ +export const WindowNames = { + BAR: "bar", + SETTINGS: "settings", + MUSIC_BOX: "music-box", + CALENDAR: "calendar", +}; diff --git a/home/natto/ags/default.nix b/home/natto/ags/default.nix new file mode 100644 index 0000000..a847476 --- /dev/null +++ b/home/natto/ags/default.nix @@ -0,0 +1,37 @@ +{ pkgs, lib, config, inputs, ... }: +let + cfg = config.programs.ags; + + deps = with pkgs; [ + sass + gawk + bash + procps + coreutils + imagemagick + config.wayland.windowManager.hyprland.package + ] ++ lib.optional config.isLaptop brightnessctl; +in +{ + imports = [ + inputs.ags.homeManagerModules.default + ]; + + programs.ags.enable = true; + + systemd.user.services.ags = { + Unit = { + Description = "Aylur's Gtk Shell"; + PartOf = [ + "tray.target" + "graphical-session.target" + ]; + }; + Service = { + Environment = "PATH=${lib.makeBinPath deps}"; + ExecStart = "${cfg.package}/bin/ags"; + Restart = "on-failure"; + }; + Install.WantedBy = [ "graphical-session.target" ]; + }; +} diff --git a/home/natto/ags/style.scss b/home/natto/ags/style.scss new file mode 100644 index 0000000..52629cd --- /dev/null +++ b/home/natto/ags/style.scss @@ -0,0 +1,5 @@ +@import "styles/global.scss"; +@import "styles/bar.scss"; +@import "styles/music-box.scss"; +@import "styles/settings.scss"; +@import "styles/calendar.scss"; diff --git a/home/natto/ags/styles/bar.scss b/home/natto/ags/styles/bar.scss new file mode 100644 index 0000000..a10771a --- /dev/null +++ b/home/natto/ags/styles/bar.scss @@ -0,0 +1,40 @@ +.bar { + background: $background; + + .hyprland { + background: none; + + button.focused { + transition-duration: 0; + color: $mauve; + box-shadow: 0 0 0 9999px rgba($mauve, 0.08) inset; + } + + button.unfocused { + background: none; + color: $flamingo; + } + } + + .music { + .music-title { + color: $sapphire; + } + + .music-controls button { + color: $yellow; + } + } + + .network .network-icon { + color: $lavender; + } + + .date-wrapper { + color: $text; + } + + .tray-button { + color: $flamingo; + } +} diff --git a/home/natto/ags/styles/calendar.scss b/home/natto/ags/styles/calendar.scss new file mode 100644 index 0000000..e387bc0 --- /dev/null +++ b/home/natto/ags/styles/calendar.scss @@ -0,0 +1,8 @@ +.calendar { + .calendar-unwrapped { + margin-top: 10px; + border: 2px solid $mauve; + border-radius: 4px; + background: rgba($base, 0.9); + } +} diff --git a/home/natto/ags/styles/global.scss b/home/natto/ags/styles/global.scss new file mode 100644 index 0000000..7a581c2 --- /dev/null +++ b/home/natto/ags/styles/global.scss @@ -0,0 +1,57 @@ +$rosewater: #f5e0dc; +$flamingo: #f2cdcd; +$pink: #f5c2e7; +$mauve: #cba6f7; +$red: #f38ba8; +$maroon: #eba0ac; +$peach: #fab387; +$yellow: #f9e2af; +$green: #a6e3a1; +$teal: #94e2d5; +$sky: #89dceb; +$sapphire: #74c7ec; +$blue: #89b4fa; +$lavender: #b4befe; +$text: #cdd6f4; +$subtext1: #bac2de; +$subtext0: #a6adc8; +$overlay2: #9399b2; +$overlay1: #7f849c; +$overlay0: #6c7086; +$surface2: #585b70; +$surface1: #45475a; +$surface0: #313244; +$base: #1e1e2e; +$mantle: #181825; +$crust: #11111b; + +$background: $base; +$foreground: $text; + +* { + font-weight: bold; +} + +button { + background: none; + border-radius: 0px; +} + +icon { + font-size: 24px; +} + +.bar, +.music-box, +.calendar, +.settings { + font-size: 16px; + font-family: "Fira Mono"; +} + +.bar { + .hyprland { + font-size: 18px; + font-family: "Lohit Gurmukhi"; + } +} diff --git a/home/natto/ags/styles/music-box.scss b/home/natto/ags/styles/music-box.scss new file mode 100644 index 0000000..e44a106 --- /dev/null +++ b/home/natto/ags/styles/music-box.scss @@ -0,0 +1,79 @@ +.music-box { + .music-box-unwrapped { + margin-top: 15px; + .music-player { + background: $base; + border: 2px solid $mauve; + border-radius: 13px; + .cover-art { + margin: 2em; + border-radius: 13px; + background-size: cover; + background-position: center; + } + .music-details { + $shadow: 0px 0px 10px rgba(black, 0.8); + padding: 2em; + .title { + font-size: 30px; + color: $pink; + text-shadow: $shadow; + } + .icon-wrapper { + .icon { + font-size: 24px; + color: $yellow; + } + .artist { + color: $yellow; + text-shadow: $shadow; + } + } + + .length-label, + .position-label { + text-shadow: $shadow; + } + .music-controls { + border-radius: 20px 8px 20px 8px; + padding: 5px; + margin: 0px 10px; + background: rgba($base, 0.5); + button { + font-size: 30px; + &:hover { + box-shadow: none; + } + } + } + + scale trough { + min-height: 10px; + margin: 0 15px; + + highlight { + background-image: linear-gradient(to right, $sapphire, $blue); + } + + slider { + border-radius: 4px; + background: $background; + margin: -10px -10px; + transition: 0.1s; + + &:hover { + border: 2px $mauve solid; + box-shadow: 0 0 0 8px rgba(255, 255, 255, 0.1); + } + + &:active { + box-shadow: + 0 0 0 1px inset, + 0 0 0 8px rgba(255, 255, 255, 0.1); + } + } + } + } + } + } +} diff --git a/home/natto/ags/styles/settings.scss b/home/natto/ags/styles/settings.scss new file mode 100644 index 0000000..1194e3e --- /dev/null +++ b/home/natto/ags/styles/settings.scss @@ -0,0 +1,112 @@ +.settings { + .settings-unwrapped { + margin-top: 10px; + border: 2px solid $mauve; + border-radius: 4px; + background: $base; + + .metrics { + & > * { + background-color: rgba(255, 255, 255, 0.03); + border-radius: 4px; + margin: 10px; + padding: 10px; + .metric-progress { + min-width: 40px; + min-height: 40px; + font-size: 4px; + margin: 4px; + .metric-icon { + font-size: 15px; + } + } + } + .cpu-metric { + color: $mauve; + } + .memory-metric { + color: $flamingo; + } + .disk-metric { + color: $pink; + } + .battery-metric { + color: $yellow; + } + } + + .settings-col { + .sliders { + $icon-size: 15px; + $scale-height: 20px; + + margin: 10px; + scale trough { + min-height: $scale-height; + min-width: 160px; + border-radius: 3px; + + highlight { + all: unset; + border-radius: 3px; + } + + slider { + $slider-height: $scale-height + 5px; + + min-width: $slider-height; + min-height: $slider-height; + border-radius: $slider-height; + background: $text; + box-shadow: 0 2px 0 0 rgba(255, 255, 255, 0.1); + } + } + + .volume { + .volume-icon { + font-size: $icon-size; + color: $green; + } + + scale trough highlight { + background-color: $green; + } + } + + .brightness { + .brightness-icon { + font-size: $icon-size; + color: $yellow; + } + + scale trough highlight { + background-color: $yellow; + } + } + } + + .settings-col-temps { + .temperature { + color: $blue; + } + .temperature-hot { + color: $red; + } + .weather { + color: $rosewater; + } + } + + .power-menu { + margin: 20px; + button { + &:hover { + color: $red; + box-shadow: none; + } + font-size: 24px; + } + } + } + } +} diff --git a/home/natto/ags/tsconfig.json b/home/natto/ags/tsconfig.json new file mode 100644 index 0000000..9a0c490 --- /dev/null +++ b/home/natto/ags/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ES2022", + "lib": [ + "ES2022" + ], + "allowJs": true, + "checkJs": false, + "strict": true, + "noImplicitAny": false, + "baseUrl": ".", + "typeRoots": [ + "./types" + ], + "skipLibCheck": true + } +} diff --git a/home/natto/ags/utils/music.js b/home/natto/ags/utils/music.js new file mode 100644 index 0000000..73bc5e4 --- /dev/null +++ b/home/natto/ags/utils/music.js @@ -0,0 +1,42 @@ +import GLib from "gi://GLib"; + +export const lengthStr = (length) => { + if (length < 0) return "0:00"; + + const min = Math.floor(length / 60); + const sec = Math.floor(length % 60); + const sec0 = sec < 10 ? "0" : ""; + return `${min}:${sec0}${sec}`; +}; + +export const blurBg = (cover) => { + if (!cover) return ""; + + const cachePath = Utils.CACHE_DIR + "/media"; + const blurPath = cachePath + "/blur"; + const bgPath = blurPath + cover.substring(cachePath.length); + + if (!GLib.file_test(bgPath, GLib.FileTest.EXISTS)) { + Utils.ensureDirectory(blurPath); + Utils.exec( + `convert ${cover} -scale 10% -blur 0x2 -resize 1000% ${bgPath}`, + ); + } + + return ` +background-image: url('${bgPath}'); +background-repeat: no-repeat; +background-position: center; +background-size: cover; + `; +}; + +export const findPlayer = (players) => { + const active = players.find((p) => p.playBackStatus === "Playing"); + + if (active) return active; + + for (const p of players) if (p) return p; + + return undefined; +}; diff --git a/home/natto/ags/utils/popup.js b/home/natto/ags/utils/popup.js new file mode 100644 index 0000000..3ac2c8e --- /dev/null +++ b/home/natto/ags/utils/popup.js @@ -0,0 +1,41 @@ +export const Padding = (name, { hexpand = true, vexpand = true } = {}) => + Widget.EventBox({ + hexpand, + vexpand, + can_focus: false, + setup: (w) => w.on("button-press-event", () => App.closeWindow(name)), + }); + +export const Revealer = ({ + name, + child, + transition = "slide_down", + transitionDuration = 200, +}) => + Widget.Box({ + css: "padding: 1px;", + child: Widget.Revealer({ + transition, + transitionDuration, + child: Widget.Box({ + child, + }), + setup: (self) => + self.hook(App, (_, window, visible) => { + if (window === name) self.reveal_child = visible; + }), + }), + }); + +export default ({ name, layout, ...props }) => + Widget.Window({ + name, + setup: (w) => w.keybind("Escape", () => App.closeWindow(name)), + visible: false, + keymode: "on-demand", + exclusivity: "normal", + layer: "top", + anchor: ["top", "bottom", "right", "left"], + child: layout, + ...props, + }); diff --git a/home/natto/ags/utils/text.js b/home/natto/ags/utils/text.js new file mode 100644 index 0000000..5db1238 --- /dev/null +++ b/home/natto/ags/utils/text.js @@ -0,0 +1,5 @@ +export const shrinkText = (str, n) => { + let newStr = str.substring(0, n); + if (str.length > n) newStr = newStr + "..."; + return newStr; +}; diff --git a/home/natto/ags/windows/bar/bluetooth.js b/home/natto/ags/windows/bar/bluetooth.js new file mode 100644 index 0000000..566e835 --- /dev/null +++ b/home/natto/ags/windows/bar/bluetooth.js @@ -0,0 +1,19 @@ +const bluetooth = await Service.import("bluetooth"); + +export default () => + Widget.Icon({ + setup: (self) => + self.hook( + bluetooth, + (self) => { + self.tooltipText = bluetooth.connected_devices + .map(({ name }) => name) + .join("\n"); + self.visible = bluetooth.connected_devices.length > 0; + }, + "notify::connected-devices", + ), + icon: bluetooth + .bind("enabled") + .as((on) => `bluetooth-${on ? "active" : "disabled"}-symbolic`), + }); diff --git a/home/natto/ags/windows/bar/hyprland.js b/home/natto/ags/windows/bar/hyprland.js new file mode 100644 index 0000000..2e2d4a2 --- /dev/null +++ b/home/natto/ags/windows/bar/hyprland.js @@ -0,0 +1,38 @@ +const hyprland = await Service.import("hyprland"); + +const gurmukhiNums = { + 1: "੧", + 2: "੨", + 3: "੩", + 4: "੪", + 5: "੫", + 6: "੬", + 7: "੭", + 8: "੮", + 9: "੯", + 10: "੦", +}; + +export default () => { + const activeId = hyprland.active.workspace.bind("id"); + const workspaces = hyprland.bind("workspaces").as((ws) => + ws + .sort((a, b) => a.id - b.id) + .map(({ id }) => + Widget.Button({ + onClicked: () => hyprland.messageAsync(`dispatch workspace ${id}`), + child: Widget.Label(gurmukhiNums[id]), + className: activeId.as( + (i) => `${i === id ? "focused" : "unfocused"}`, + ), + cursor: "pointer", + }), + ), + ); + + return Widget.Box({ + css: "padding: 1px;", + className: "hyprland", + children: workspaces, + }); +}; diff --git a/home/natto/ags/windows/bar/index.js b/home/natto/ags/windows/bar/index.js new file mode 100644 index 0000000..8c3c00a --- /dev/null +++ b/home/natto/ags/windows/bar/index.js @@ -0,0 +1,56 @@ +import Hyprland from "./hyprland.js"; +import Music from "./music.js"; +import Tray from "./tray.js"; +import Time from "./time.js"; +import Network from "./network.js"; +import Bluetooth from "./bluetooth.js"; +import Settings from "./settings.js"; + +import { WindowNames } from "../../constants.js"; + +const { BAR } = WindowNames; + +const Left = () => { + return Widget.Box({ + className: "bar-left", + spacing: 8, + children: [Hyprland()], + }); +}; + +const Center = (monitor) => { + return Widget.Box({ + className: "bar-center", + spacing: 8, + children: [Music(monitor)], + }); +}; + +const Right = (monitor) => { + return Widget.Box({ + className: "bar-right", + hpack: "end", + spacing: 10, + children: [ + Tray(), + Bluetooth(), + Network(), + Time(monitor), + Settings(monitor), + ], + }); +}; + +export default (monitor = 0) => + Widget.Window({ + name: `${BAR}-${monitor}`, + className: BAR, + monitor, + anchor: ["top", "left", "right"], + exclusivity: "exclusive", + child: Widget.CenterBox({ + startWidget: Left(), + centerWidget: Center(monitor), + endWidget: Right(monitor), + }), + }); diff --git a/home/natto/ags/windows/bar/music.js b/home/natto/ags/windows/bar/music.js new file mode 100644 index 0000000..32c93bd --- /dev/null +++ b/home/natto/ags/windows/bar/music.js @@ -0,0 +1,65 @@ +import Controls from "../music-box/music-controls.js"; +import { shrinkText } from "../../utils/text.js"; +import { findPlayer } from "../../utils/music.js"; +import { WindowNames } from "../../constants.js"; + +const mpris = await Service.import("mpris"); +const players = mpris.bind("players"); + +/** @param {import('types/service/mpris').MprisPlayer} player */ +const Player = (player, monitor) => { + const revealer = Widget.Revealer({ + revealChild: false, + transitionDuration: 300, + transition: "slide_left", + child: Controls(player), + }); + + return Widget.EventBox({ + visible: player.bus_name === findPlayer(mpris.players).bus_name, + cursor: "pointer", + setup: (self) => { + self.on("leave-notify-event", () => { + revealer.reveal_child = false; + }); + self.on("enter-notify-event", () => { + revealer.reveal_child = true; + }); + }, + child: Widget.Box({ + className: "music", + children: [ + Widget.Button({ + onClicked: () => + App.toggleWindow(`${WindowNames.MUSIC_BOX}-${monitor}`), + className: "music-title", + child: Widget.Label().hook(player, (self) => { + self.tooltip_text = player.track_title; + self.label = shrinkText(self.tooltip_text, 50); + }), + }), + revealer, + ], + }), + }) + .hook( + mpris, + (self, bus_name) => { + self.visible = player.bus_name === bus_name; + }, + "player-changed", + ) + .hook( + mpris, + (self) => { + self.visible = player === findPlayer(mpris.players); + }, + "player-closed", + ); +}; + +export default (monitor) => + Widget.Box({ + visible: players.as((p) => p.length > 0), + children: players.as((ps) => ps.map((p) => Player(p, monitor))), + }); diff --git a/home/natto/ags/windows/bar/network.js b/home/natto/ags/windows/bar/network.js new file mode 100644 index 0000000..89a8c54 --- /dev/null +++ b/home/natto/ags/windows/bar/network.js @@ -0,0 +1,40 @@ +const network = await Service.import("network"); + +const WifiIndicator = () => + Widget.Box({ + tooltipText: network.wifi.bind("state").as((s) => `State: ${s}`), + children: [ + Widget.Icon({ + className: "network-icon", + icon: network.wifi.bind("icon_name"), + }), + Widget.Label({ + visible: network.wifi.bind("ssid"), + label: network.wifi.bind("ssid"), + }), + ], + }); + +const WiredIndicator = () => + Widget.Icon({ + className: "network-icon", + tooltipText: network.wired.bind("internet").as((a) => `Internet: ${a}`), + icon: network.wired.bind("icon_name"), + }); + +export default () => + Widget.Stack({ + className: "network", + children: { + wifi: WifiIndicator(), + wired: WiredIndicator(), + }, + shown: Utils.merge( + [network.bind("primary"), network.wired.bind("state")], + (primary, wired) => { + if (primary) return primary; + if (wired == "activated") return "wired"; + return "wifi"; + }, + ), + }); diff --git a/home/natto/ags/windows/bar/settings.js b/home/natto/ags/windows/bar/settings.js new file mode 100644 index 0000000..f33e3f7 --- /dev/null +++ b/home/natto/ags/windows/bar/settings.js @@ -0,0 +1,8 @@ +import { WindowNames } from "../../constants.js"; + +export default (monitor) => + Widget.Button({ + child: Widget.Icon("open-menu-symbolic"), + onPrimaryClick: () => + App.toggleWindow(`${WindowNames.SETTINGS}-${monitor}`), + }); diff --git a/home/natto/ags/windows/bar/time.js b/home/natto/ags/windows/bar/time.js new file mode 100644 index 0000000..d9b8d13 --- /dev/null +++ b/home/natto/ags/windows/bar/time.js @@ -0,0 +1,42 @@ +const time = Variable("", { + poll: [1000, 'date "+%H:%M:%S"'], +}); + +const getDate = () => " " + Utils.exec('date "+%a, %b %e"'); +const date = Variable(getDate()); + +export default () => { + const revealer = Widget.Revealer({ + revealChild: false, + transitionDuration: 300, + transition: "slide_left", + child: Widget.Label({ + label: date.bind(), + }), + }); + + return Widget.EventBox({ + cursor: "pointer", + setup: (self) => { + self.on("leave-notify-event", () => { + revealer.reveal_child = false; + }); + self.on("enter-notify-event", () => { + date.value = getDate(); + revealer.reveal_child = true; + }); + }, + child: Widget.Button({ + className: "date-wrapper", + onPrimaryClick: () => App.toggleWindow("calendar-0"), + child: Widget.Box({ + children: [ + Widget.Label({ + label: time.bind(), + }), + revealer, + ], + }), + }), + }); +}; diff --git a/home/natto/ags/windows/bar/tray.js b/home/natto/ags/windows/bar/tray.js new file mode 100644 index 0000000..00b1a11 --- /dev/null +++ b/home/natto/ags/windows/bar/tray.js @@ -0,0 +1,17 @@ +const systemtray = await Service.import("systemtray"); + +const SysTrayItem = (item) => + Widget.Button({ + className: "system-tray-item", + child: Widget.Icon().bind("icon", item, "icon"), + tooltipMarkup: item.bind("tooltip_markup"), + onPrimaryClick: (_, event) => item.activate(event), + onSecondaryClick: (_, event) => item.openMenu(event), + cursor: "pointer", + }); + +export default () => + Widget.Box({ + className: "system-tray-unwrapped", + children: systemtray.bind("items").as((i) => i.map(SysTrayItem)), + }); diff --git a/home/natto/ags/windows/calendar.js b/home/natto/ags/windows/calendar.js new file mode 100644 index 0000000..cce5567 --- /dev/null +++ b/home/natto/ags/windows/calendar.js @@ -0,0 +1,37 @@ +import Popup, { Padding, Revealer } from "../utils/popup.js"; +import { WindowNames } from "../constants.js"; + +const Tray = Widget.Calendar({ + className: "calendar-unwrapped", + showDayNames: true, + showHeading: true, +}); + +export default (monitor = 0) => { + const { CALENDAR } = WindowNames; + const name = `${CALENDAR}-${monitor}`; + + return Popup({ + name, + className: CALENDAR, + monitor, + layout: Widget.Box({ + children: [ + Padding(name), + Widget.Box({ + hexpand: false, + vertical: true, + children: [ + Revealer({ + name, + child: Tray, + transition: "slide_down", + transitionDuration: 400, + }), + Padding(name), + ], + }), + ], + }), + }); +}; diff --git a/home/natto/ags/windows/music-box/index.js b/home/natto/ags/windows/music-box/index.js new file mode 100644 index 0000000..da2383f --- /dev/null +++ b/home/natto/ags/windows/music-box/index.js @@ -0,0 +1,183 @@ +// Mostly taken from https://github.com/Aylur/ags/blob/11150225e62462bcd431d1e55185e810190a730a/example/media-widget/Media.js + +import Popup, { Padding, Revealer } from "../../utils/popup.js"; +import { shrinkText } from "../../utils/text.js"; +import { lengthStr, blurBg } from "../../utils/music.js"; +import { findPlayer } from "../../utils/music.js"; +import { WindowNames } from "../../constants.js"; +import Controls from "./music-controls.js"; + +const FALLBACK_ICON = "audio-x-generic-symbolic"; +const { MUSIC_BOX } = WindowNames; + +const mpris = await Service.import("mpris"); + +const Player = (player) => { + const img = Widget.Box({ + visible: player.bind("cover_path"), + className: "cover-art", + vpack: "start", + css: player + .bind("cover_path") + .as( + (p) => + (p ? "min-width: 200px; min-height: 200px;" : "") + + `background-image: url('${p}');`, + ), + }); + + const title = Widget.Label({ + className: "title", + wrap: true, + hpack: "start", + label: player.bind("track_title").as((t) => shrinkText(t, 40)), + }); + + const artist = Widget.Label({ + className: "artist", + wrap: true, + hpack: "start", + label: player.bind("track_artists").as((a) => shrinkText(a.join(", "), 80)), + }); + + const positionSlider = Widget.Slider({ + className: "position", + drawValue: false, + onChange: ({ value }) => (player.position = value * player.length), + visible: player.bind("length").as((l) => l > 0), + setup: (self) => { + const update = () => { + if (self.dragging) return; + + const value = player.position / player.length; + self.value = value > 0 ? value : 0; + }; + + self + .hook(player, update) + .hook(player, update, "position") + .poll(1000, update); + }, + }); + + const positionLabel = Widget.Label({ + ypad: 0, + hpack: "start", + className: "position-label", + setup: (self) => { + const update = (_, time) => { + self.label = lengthStr(time || player.position); + self.visible = player.length > 0; + }; + + self.hook(player, update, "position"); + self.poll(1000, update); + }, + }); + + const lengthLabel = Widget.Label({ + ypad: 0, + hpack: "end", + className: "length-label", + visible: player.bind("length").as((l) => l > 0), + label: player.bind("length").as(lengthStr), + }); + + const icon = Widget.Icon({ + className: "icon", + hexpand: true, + hpack: "end", + vpack: "start", + tooltipText: player.identity || "", + icon: player.bind("entry").as((entry) => { + const name = `${entry}-symbolic`; + return Utils.lookUpIcon(name) ? name : FALLBACK_ICON; + }), + }); + + return Widget.Box( + { + className: "music-player", + visible: player.bus_name === findPlayer(mpris.players).bus_name, + css: player.bind("cover_path").as(blurBg), + }, + img, + Widget.CenterBox({ + className: "music-details", + vertical: true, + hexpand: true, + spacing: 25, + startWidget: Widget.Box( + { + vertical: true, + spacing: 6, + }, + title, + Widget.Box({ + className: "icon-wrapper", + spacing: 10, + children: [artist, icon], + }), + ), + centerWidget: positionSlider, + endWidget: Widget.CenterBox({ + spacing: 6, + startWidget: positionLabel, + centerWidget: Controls(player), + endWidget: lengthLabel, + }), + }), + ) + .hook( + mpris, + (self, bus_name) => { + self.visible = player.bus_name === bus_name; + }, + "player-changed", + ) + .hook( + mpris, + (self) => { + self.visible = player.bus_name === findPlayer(mpris.players).bus_name; + }, + "player-closed", + ); +}; + +const PlayerBox = () => + Widget.Box({ + className: `${MUSIC_BOX}-unwrapped`, + vertical: true, + css: "padding: 1px;", + children: mpris.bind("players").as((ps) => ps.map(Player)), + }); + +export default (monitor = 0) => { + const name = `${MUSIC_BOX}-${monitor}`; + + return Popup({ + name, + className: MUSIC_BOX, + monitor, + layout: Widget.Box({ + children: [ + Padding(name), // left + Widget.Box({ + hexpand: false, + vexpand: false, + vertical: true, + children: [ + Revealer({ + name, + child: PlayerBox(), + transition: "slide_down", + transitionDuration: 400, + }), + Padding(name), // down + ], + }), + Padding(name), // right + ], + }), + }); +}; diff --git a/home/natto/ags/windows/music-box/music-controls.js b/home/natto/ags/windows/music-box/music-controls.js new file mode 100644 index 0000000..fd54ca6 --- /dev/null +++ b/home/natto/ags/windows/music-box/music-controls.js @@ -0,0 +1,47 @@ +const PLAY_ICON = "media-playback-start-symbolic"; +const PAUSE_ICON = "media-playback-pause-symbolic"; +const PREV_ICON = "go-previous-symbolic"; +const NEXT_ICON = "go-next-symbolic"; + +export default (player) => { + const playPause = Widget.Button({ + class_name: "play-pause", + on_clicked: () => player.playPause(), + visible: player.bind("can_play"), + cursor: "pointer", + child: Widget.Icon({ + icon: player.bind("play_back_status").transform((s) => { + switch (s) { + case "Playing": + return PAUSE_ICON; + case "Paused": + case "Stopped": + return PLAY_ICON; + } + }), + }), + }); + + const prev = Widget.Button({ + yalign: 0.5, + onClicked: () => player.previous(), + visible: player.bind("can_go_prev"), + child: Widget.Icon(PREV_ICON), + cursor: "pointer", + }); + + const next = Widget.Button({ + onClicked: () => player.next(), + visible: player.bind("can_go_next"), + child: Widget.Icon(NEXT_ICON), + cursor: "pointer", + }); + + return Widget.CenterBox({ + vertical: true, + centerWidget: Widget.Box({ + className: "music-controls", + children: [prev, playPause, next], + }), + }); +}; diff --git a/home/natto/ags/windows/settings/audio.js b/home/natto/ags/windows/settings/audio.js new file mode 100644 index 0000000..21f963f --- /dev/null +++ b/home/natto/ags/windows/settings/audio.js @@ -0,0 +1,69 @@ +const audio = await Service.import("audio"); + +export default () => { + const isSpeaker = Variable(true); + + /** @param {'speaker' | 'microphone'} type */ + const VolumeSlider = (type = "speaker") => + Widget.Slider({ + hexpand: true, + drawValue: false, + onChange: ({ value }) => (audio[type].volume = value), + value: audio[type].bind("volume"), + }); + + const speakerSlider = VolumeSlider("speaker"); + const micSlider = VolumeSlider("microphone"); + + const speakerIndicator = Widget.Button({ + on_clicked: () => (audio.speaker.is_muted = !audio.speaker.is_muted), + child: Widget.Icon().hook(audio.speaker, (self) => { + self.className = "volume-icon"; + const vol = audio.speaker.volume * 100; + let icon = [ + [101, "overamplified"], + [67, "high"], + [34, "medium"], + [1, "low"], + [0, "muted"], + ].find(([threshold]) => threshold <= vol)?.[1]; + + if (audio.speaker.is_muted) icon = "muted"; + + self.icon = `audio-volume-${icon}-symbolic`; + self.tooltip_text = `Volume ${Math.floor(vol)}%`; + }), + }); + + const micIndicator = Widget.Button({ + on_clicked: () => (audio.microphone.is_muted = !audio.microphone.is_muted), + child: Widget.Icon().hook(audio.microphone, (self) => { + self.className = "volume-icon"; + const vol = audio.microphone.volume * 100; + let icon = [ + [67, "high"], + [34, "medium"], + [1, "low"], + [0, "muted"], + ].find(([threshold]) => threshold <= vol)?.[1]; + + if (audio.microphone.is_muted) icon = "muted"; + + self.icon = `microphone-sensitivity-${icon}-symbolic`; + self.tooltip_text = `Volume ${Math.floor(vol)}%`; + }), + }); + + return Widget.EventBox({ + cursor: "pointer", + className: "volume", + onSecondaryClick: () => (isSpeaker.value = !isSpeaker.value), + child: Widget.Stack({ + children: { + speaker: Widget.Box({}, speakerSlider, speakerIndicator), + microphone: Widget.Box({}, micSlider, micIndicator), + }, + shown: isSpeaker.bind().as((s) => (s ? "speaker" : "microphone")), + }), + }); +}; diff --git a/home/natto/ags/windows/settings/backlight.js b/home/natto/ags/windows/settings/backlight.js new file mode 100644 index 0000000..1ad7535 --- /dev/null +++ b/home/natto/ags/windows/settings/backlight.js @@ -0,0 +1,48 @@ +const hasBacklight = Variable(Utils.exec("ls /sys/class/backlight")); + +export default () => { + const getBrightness = () => { + try { + return ( + Number(Utils.exec("brightnessctl get")) / + Number(Utils.exec("brightnessctl max")) + ); + } catch { + console.log("settings/backlight: failed to get brightness"); + } + return 0; + }; + + const setBrightness = (b) => { + if (b < 0.05) b = 0.05; + else if (b > 1) b = 1; + + Utils.exec(`brightnessctl set ${b * 100}%`); + }; + const brightness = Variable(getBrightness()); + + const Slider = Widget.Slider({ + hexpand: true, + drawValue: false, + onChange: ({ value }) => setBrightness(value), + value: brightness.bind(), + }); + + const Indicator = Widget.Button({ + on_clicked: brightness.bind().as((b) => () => { + if (b <= 0.5) brightness.value = 1; + else brightness.value = 0.5; + }), + child: Widget.Icon().hook(brightness, (self) => { + self.className = "brightness-icon"; + self.icon = `display-brightness-symbolic`; + self.tooltip_text = `Brightness: ${Math.floor(brightness.value * 100)}%`; + }), + }); + + return Widget.Box({ + className: "brightness", + visible: hasBacklight.bind().as((b) => !!b), + children: [Slider, Indicator], + }); +}; diff --git a/home/natto/ags/windows/settings/index.js b/home/natto/ags/windows/settings/index.js new file mode 100644 index 0000000..36f3ff4 --- /dev/null +++ b/home/natto/ags/windows/settings/index.js @@ -0,0 +1,72 @@ +import Popup, { Padding, Revealer } from "../../utils/popup.js"; +import { WindowNames } from "../../constants.js"; +import Audio from "./audio.js"; +import Backlight from "./backlight.js"; +import { + cpuMetric, + memoryMetric, + diskMetric, + batteryMetric, +} from "./metrics.js"; +import Temperature from "./temperature.js"; +import Weather from "./weather.js"; +import PowerMenu from "./power-menu.js"; + +const metrics = Widget.Box({ + className: "metrics", + vertical: true, + children: [cpuMetric, memoryMetric, diskMetric, batteryMetric], +}); + +const sliders = Widget.Box({ + className: "sliders", + vertical: true, + children: [Audio(), Backlight()], +}); + +const settingsCol = Widget.CenterBox({ + className: "settings-col", + vertical: true, + spacing: 8, + startWidget: sliders, + centerWidget: Widget.CenterBox({ + className: "settings-col-temps", + startWidget: Temperature(), + endWidget: Weather(), + }), + endWidget: PowerMenu(), +}); + +const settings = Widget.Box({ + className: "settings-unwrapped", + children: [metrics, settingsCol], +}); + +export default (monitor = 0) => { + const { SETTINGS } = WindowNames; + const name = `${SETTINGS}-${monitor}`; + + return Popup({ + name, + className: SETTINGS, + monitor, + layout: Widget.Box({ + children: [ + Padding(name), + Widget.Box({ + hexpand: false, + vertical: true, + children: [ + Revealer({ + name, + child: settings, + transition: "slide_down", + transitionDuration: 400, + }), + Padding(name), + ], + }), + ], + }), + }); +}; diff --git a/home/natto/ags/windows/settings/metrics.js b/home/natto/ags/windows/settings/metrics.js new file mode 100644 index 0000000..fa12bf9 --- /dev/null +++ b/home/natto/ags/windows/settings/metrics.js @@ -0,0 +1,104 @@ +const battery = await Service.import("battery"); + +const divide = ([total, free]) => free / total; + +const cpuValue = Variable(0, { + poll: [ + 2000, + "top -b -n 1", + (out) => + divide([ + 100, + out + .split("\n") + .find((line) => line.includes("Cpu(s)")) + .split(/\s+/)[1] + .replace(",", "."), + ]), + ], +}); + +const memoryValue = Variable([0, 0], { + poll: [ + 2000, + "free", + (out) => { + const data = out + .split("\n") + .find((line) => line.includes("Mem:")) + .split(/\s+/) + .splice(1, 2); + return [(data[1] / (1024 * 1024)).toFixed(2), divide(data)]; + }, + ], +}); + +const diskValue = Variable(["0G", "0%"], { + poll: [ + 120000, + "df -kh /", + (out) => out.split("\n")[1].split(/\s+/).splice(3, 2), + ], +}); + +const mkMetric = ({ + className, + tooltipText, + value, + label, + icon, + visible = true, +}) => + Widget.Box( + { + spacing: 10, + className, + tooltipText, + visible, + }, + Widget.CircularProgress({ + className: "metric-progress", + child: Widget.Icon({ + icon, + className: "metric-icon", + }), + value, + }), + Widget.Label({ + wrap: true, + label, + }), + ); + +export const cpuMetric = mkMetric({ + className: "cpu-metric", + tooltipText: cpuValue.bind().as((c) => `CPU: ${(c * 100).toFixed(2)}%`), + value: cpuValue.bind(), + label: cpuValue.bind().as((c) => `${(c * 100).toFixed(2)}%`), + icon: "cpu-symbolic", +}); + +export const memoryMetric = mkMetric({ + className: "memory-metric", + tooltipText: memoryValue.bind().as((m) => `RAM :${m[0]}G`), + value: memoryValue.bind().as((m) => m[1]), + label: memoryValue.bind().as((m) => `${m[0]}G`), + icon: "memory-symbolic", +}); + +export const diskMetric = mkMetric({ + className: "disk-metric", + tooltipText: diskValue.bind().as((d) => `Free Space :${d[0]}`), + value: diskValue.bind().as((d) => Number(d[1]) / 100), + label: diskValue.bind().as((d) => d[1]), + icon: "drive-harddisk-symbolic", +}); + +export const batteryMetric = mkMetric({ + className: "battery-metric", + tooltipText: battery.bind("percent").as((p) => `Battery: ${p}%`), + value: battery.bind("percent").as((p) => (p > 0 ? p / 100 : 0)), + label: battery.bind("percent").as((p) => `${p}%`), + icon: battery.bind("icon_name"), + visible: battery.bind("available"), +}); diff --git a/home/natto/ags/windows/settings/power-menu.js b/home/natto/ags/windows/settings/power-menu.js new file mode 100644 index 0000000..bce8eac --- /dev/null +++ b/home/natto/ags/windows/settings/power-menu.js @@ -0,0 +1,94 @@ +export default () => { + const isLocked = Variable(true); + const power = Variable("poweroff"); + const suspend = Variable("sleep"); + + const cursor = "pointer"; + + const unlockButton = Widget.Button({ + onPrimaryClick: () => { + isLocked.value = false; + }, + tooltipText: "Unock power menu", + child: Widget.Icon("system-lock-screen-symbolic"), + cursor, + }); + + const lockButton = Widget.Button({ + onPrimaryClick: () => { + isLocked.value = true; + }, + tooltipText: "Lock power menu", + child: Widget.Icon("system-lock-screen-symbolic"), + cursor, + }); + + const poweroffButton = Widget.Button({ + onPrimaryClick: () => { + Utils.exec("poweroff"); + }, + onSecondaryClick: () => (power.value = "reboot"), + tooltipText: "Shutdown", + child: Widget.Icon("system-shutdown-symbolic"), + cursor, + }); + + const rebootButton = Widget.Button({ + onPrimaryClick: () => { + Utils.exec("reboot"); + }, + onSecondaryClick: () => (power.value = "poweroff"), + tooltipText: "Reboot", + child: Widget.Icon("system-reboot-symbolic"), + cursor, + }); + + const sleepButton = Widget.Button({ + onPrimaryClick: () => { + Utils.exec("systemctl suspend"); + }, + onSecondaryClick: () => (suspend.value = "hibernate"), + tooltipText: "Sleep", + child: Widget.Icon("weather-clear-night-symbolic"), + cursor, + }); + + const hibernateButton = Widget.Button({ + onPrimaryClick: () => { + Utils.exec("systemctl hibernate"); + }, + onSecondaryClick: () => (suspend.value = "sleep"), + tooltipText: "Hibernate", + child: Widget.Icon("computer-symbolic"), + cursor, + }); + + const powerStack = Widget.Stack({ + children: { + poweroff: poweroffButton, + reboot: rebootButton, + }, + shown: power.bind(), + }); + + const suspendStack = Widget.Stack({ + children: { + sleep: sleepButton, + hibernate: hibernateButton, + }, + shown: suspend.bind(), + }); + + return Widget.Stack({ + className: "power-menu", + children: { + locked: Widget.CenterBox({ centerWidget: unlockButton }), + unlocked: Widget.CenterBox({ + startWidget: powerStack, + centerWidget: lockButton, + endWidget: suspendStack, + }), + }, + shown: isLocked.bind().as((l) => (l ? "locked" : "unlocked")), + }); +}; diff --git a/home/natto/ags/windows/settings/temperature.js b/home/natto/ags/windows/settings/temperature.js new file mode 100644 index 0000000..bfb0453 --- /dev/null +++ b/home/natto/ags/windows/settings/temperature.js @@ -0,0 +1,62 @@ +export default () => { + const getThermalZone = () => { + try { + return Utils.exec([ + "bash", + "-c", + `awk '{print FILENAME ":" $0}' /sys/class/thermal/thermal_zone*/type`, + ]) + .split("\n") + .find((line) => line.includes("x86_pkg_temp")) + .split(":")[0] + .slice(0, -4); + } catch (e) { + console.error(e); + console.log("settings/temperature: cannot get thermal zone"); + } + return undefined; + }; + + const thermalZone = Variable(getThermalZone()); + + const tempValue = thermalZone.value + ? Variable(0, { + poll: [ + 5000, + () => { + try { + return ( + Utils.readFile(`${thermalZone.value}/temp`) / 1000 + ).toFixed(2); + } catch { + console.log( + "settings/temperature: specified thermal zone does not exist", + ); + } + return 0; + }, + ], + }) + : Variable(undefined); + + return Widget.CenterBox({ + vertical: true, + visible: thermalZone.bind().as((t) => !!t), + centerWidget: Widget.Box( + { + vertical: true, + spacing: 8, + tooltipText: tempValue.bind().as((t) => `CPU Temperature: ${t}°C`), + className: tempValue + .bind() + .as((t) => `temperature${t > 65 ? "-hot" : ""}`), + }, + Widget.Icon({ + icon: "sensors-temperature-symbolic", + }), + Widget.Label({ + label: tempValue.bind().as((t) => `${t}°C`), + }), + ), + }); +}; diff --git a/home/natto/ags/windows/settings/weather.js b/home/natto/ags/windows/settings/weather.js new file mode 100644 index 0000000..05e97fe --- /dev/null +++ b/home/natto/ags/windows/settings/weather.js @@ -0,0 +1,78 @@ +const conditionIcons = { + Clear: "clear", + Sunny: "clear", + "Partly Cloudy": "few-clouds", + Cloudy: "overcast", + Overcast: "overcast", + "Light Rain": "showers-scattered", + Drizzle: "showers-scattered", + Rain: "showers", + "Heavy Rain": "showers", + Showers: "showers", + Thunderstorm: "storm", + Snow: "snow", + "Light Snow": "snow", + "Heavy Snow": "snow", + Mist: "fog", + Fog: "fog", + Haze: "fog", + Dust: "fog", + Smoke: "fog", + Sand: "fog", + Wind: "windy", + Tornado: "tornado", + undefined: "clear", +}; + +const fetchWeather = async () => { + return await Utils.fetch("http://wttr.in/?format=j1") + .then((res) => res.json()) + .then((j) => j["current_condition"][0]) + .catch((e) => { + console.error(e); + console.log("settings/weather: error fetching weather data"); + }); +}; + +export default () => { + const data = Variable(undefined, { + poll: [600000, async () => await fetchWeather()], + }); + + return Widget.Box( + { + vertical: true, + visible: data.bind().as((d) => !!d), + className: "weather", + }, + Widget.Icon({ + icon: data.bind().as((d) => { + const condition = d?.["weatherDesc"]?.[0]?.["value"]; + return `weather-${conditionIcons[condition]}-symbolic`; + }), + }), + Widget.Label({ + label: data.bind().as((d) => { + const conditions = d?.["weatherDesc"]?.map((w) => w["value"]) || []; + + return conditions.join(" "); + }), + }), + Widget.Label({ + label: data.bind().as((d) => { + const temperature = d?.["temp_C"]; + const feelsLike = d?.["FeelsLikeC"]; + + return `${temperature}°C (${feelsLike}°C)`; + }), + }), + Widget.Label({ + label: data.bind().as((d) => { + const humidity = d?.["humidity"]; + const precipitation = d?.["precipMM"]; + + return `${humidity}%, ${precipitation}mm`; + }), + }), + ); +}; diff --git a/home/natto/wayland.nix b/home/natto/wayland.nix index dbf94d2..0c35310 100644 --- a/home/natto/wayland.nix +++ b/home/natto/wayland.nix @@ -1,6 +1,7 @@ { pkgs, config, conf, inputs, ... }: { imports = [ + ./ags # ./eww ];