home/natto/ags: init
Signed-off-by: Amneesh Singh <natto@weirdnatto.in>
This commit is contained in:
		
							
								
								
									
										15
									
								
								home/natto/ags/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								home/natto/ags/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
							
								
								
									
										31
									
								
								home/natto/ags/config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								home/natto/ags/config.js
									
									
									
									
									
										Normal file
									
								
							@@ -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 {};
 | 
			
		||||
							
								
								
									
										6
									
								
								home/natto/ags/constants.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								home/natto/ags/constants.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
export const WindowNames = {
 | 
			
		||||
  BAR: "bar",
 | 
			
		||||
  SETTINGS: "settings",
 | 
			
		||||
  MUSIC_BOX: "music-box",
 | 
			
		||||
  CALENDAR: "calendar",
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										37
									
								
								home/natto/ags/default.nix
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								home/natto/ags/default.nix
									
									
									
									
									
										Normal file
									
								
							@@ -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" ];
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										5
									
								
								home/natto/ags/style.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								home/natto/ags/style.scss
									
									
									
									
									
										Normal file
									
								
							@@ -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";
 | 
			
		||||
							
								
								
									
										40
									
								
								home/natto/ags/styles/bar.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								home/natto/ags/styles/bar.scss
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								home/natto/ags/styles/calendar.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								home/natto/ags/styles/calendar.scss
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
.calendar {
 | 
			
		||||
  .calendar-unwrapped {
 | 
			
		||||
    margin-top: 10px;
 | 
			
		||||
    border: 2px solid $mauve;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    background: rgba($base, 0.9);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										57
									
								
								home/natto/ags/styles/global.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								home/natto/ags/styles/global.scss
									
									
									
									
									
										Normal file
									
								
							@@ -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";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										79
									
								
								home/natto/ags/styles/music-box.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								home/natto/ags/styles/music-box.scss
									
									
									
									
									
										Normal file
									
								
							@@ -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);
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										112
									
								
								home/natto/ags/styles/settings.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								home/natto/ags/styles/settings.scss
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								home/natto/ags/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								home/natto/ags/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
{
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    "target": "ES2022",
 | 
			
		||||
    "module": "ES2022",
 | 
			
		||||
    "lib": [
 | 
			
		||||
      "ES2022"
 | 
			
		||||
    ],
 | 
			
		||||
    "allowJs": true,
 | 
			
		||||
    "checkJs": false,
 | 
			
		||||
    "strict": true,
 | 
			
		||||
    "noImplicitAny": false,
 | 
			
		||||
    "baseUrl": ".",
 | 
			
		||||
    "typeRoots": [
 | 
			
		||||
      "./types"
 | 
			
		||||
    ],
 | 
			
		||||
    "skipLibCheck": true
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										42
									
								
								home/natto/ags/utils/music.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								home/natto/ags/utils/music.js
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										41
									
								
								home/natto/ags/utils/popup.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								home/natto/ags/utils/popup.js
									
									
									
									
									
										Normal file
									
								
							@@ -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,
 | 
			
		||||
  });
 | 
			
		||||
							
								
								
									
										5
									
								
								home/natto/ags/utils/text.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								home/natto/ags/utils/text.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
export const shrinkText = (str, n) => {
 | 
			
		||||
  let newStr = str.substring(0, n);
 | 
			
		||||
  if (str.length > n) newStr = newStr + "...";
 | 
			
		||||
  return newStr;
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										19
									
								
								home/natto/ags/windows/bar/bluetooth.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								home/natto/ags/windows/bar/bluetooth.js
									
									
									
									
									
										Normal file
									
								
							@@ -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`),
 | 
			
		||||
  });
 | 
			
		||||
							
								
								
									
										38
									
								
								home/natto/ags/windows/bar/hyprland.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								home/natto/ags/windows/bar/hyprland.js
									
									
									
									
									
										Normal file
									
								
							@@ -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,
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										56
									
								
								home/natto/ags/windows/bar/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								home/natto/ags/windows/bar/index.js
									
									
									
									
									
										Normal file
									
								
							@@ -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),
 | 
			
		||||
    }),
 | 
			
		||||
  });
 | 
			
		||||
							
								
								
									
										65
									
								
								home/natto/ags/windows/bar/music.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								home/natto/ags/windows/bar/music.js
									
									
									
									
									
										Normal file
									
								
							@@ -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))),
 | 
			
		||||
  });
 | 
			
		||||
							
								
								
									
										40
									
								
								home/natto/ags/windows/bar/network.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								home/natto/ags/windows/bar/network.js
									
									
									
									
									
										Normal file
									
								
							@@ -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";
 | 
			
		||||
      },
 | 
			
		||||
    ),
 | 
			
		||||
  });
 | 
			
		||||
							
								
								
									
										8
									
								
								home/natto/ags/windows/bar/settings.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								home/natto/ags/windows/bar/settings.js
									
									
									
									
									
										Normal file
									
								
							@@ -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}`),
 | 
			
		||||
  });
 | 
			
		||||
							
								
								
									
										42
									
								
								home/natto/ags/windows/bar/time.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								home/natto/ags/windows/bar/time.js
									
									
									
									
									
										Normal file
									
								
							@@ -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,
 | 
			
		||||
        ],
 | 
			
		||||
      }),
 | 
			
		||||
    }),
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										17
									
								
								home/natto/ags/windows/bar/tray.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								home/natto/ags/windows/bar/tray.js
									
									
									
									
									
										Normal file
									
								
							@@ -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)),
 | 
			
		||||
  });
 | 
			
		||||
							
								
								
									
										37
									
								
								home/natto/ags/windows/calendar.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								home/natto/ags/windows/calendar.js
									
									
									
									
									
										Normal file
									
								
							@@ -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),
 | 
			
		||||
          ],
 | 
			
		||||
        }),
 | 
			
		||||
      ],
 | 
			
		||||
    }),
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										183
									
								
								home/natto/ags/windows/music-box/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								home/natto/ags/windows/music-box/index.js
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
      ],
 | 
			
		||||
    }),
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										47
									
								
								home/natto/ags/windows/music-box/music-controls.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								home/natto/ags/windows/music-box/music-controls.js
									
									
									
									
									
										Normal file
									
								
							@@ -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],
 | 
			
		||||
    }),
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										69
									
								
								home/natto/ags/windows/settings/audio.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								home/natto/ags/windows/settings/audio.js
									
									
									
									
									
										Normal file
									
								
							@@ -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")),
 | 
			
		||||
    }),
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										48
									
								
								home/natto/ags/windows/settings/backlight.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								home/natto/ags/windows/settings/backlight.js
									
									
									
									
									
										Normal file
									
								
							@@ -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],
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										72
									
								
								home/natto/ags/windows/settings/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								home/natto/ags/windows/settings/index.js
									
									
									
									
									
										Normal file
									
								
							@@ -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),
 | 
			
		||||
          ],
 | 
			
		||||
        }),
 | 
			
		||||
      ],
 | 
			
		||||
    }),
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										104
									
								
								home/natto/ags/windows/settings/metrics.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								home/natto/ags/windows/settings/metrics.js
									
									
									
									
									
										Normal file
									
								
							@@ -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"),
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										94
									
								
								home/natto/ags/windows/settings/power-menu.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								home/natto/ags/windows/settings/power-menu.js
									
									
									
									
									
										Normal file
									
								
							@@ -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")),
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										62
									
								
								home/natto/ags/windows/settings/temperature.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								home/natto/ags/windows/settings/temperature.js
									
									
									
									
									
										Normal file
									
								
							@@ -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`),
 | 
			
		||||
      }),
 | 
			
		||||
    ),
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										78
									
								
								home/natto/ags/windows/settings/weather.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								home/natto/ags/windows/settings/weather.js
									
									
									
									
									
										Normal file
									
								
							@@ -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`;
 | 
			
		||||
      }),
 | 
			
		||||
    }),
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
{ pkgs, config, conf, inputs, ... }:
 | 
			
		||||
{
 | 
			
		||||
  imports = [
 | 
			
		||||
    ./ags
 | 
			
		||||
    # ./eww
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user