testing ags
This commit is contained in:
12
flake.lock
generated
12
flake.lock
generated
@@ -554,11 +554,11 @@
|
||||
},
|
||||
"nixpkgs_3": {
|
||||
"locked": {
|
||||
"lastModified": 1720768451,
|
||||
"narHash": "sha256-EYekUHJE2gxeo2pM/zM9Wlqw1Uw2XTJXOSAO79ksc4Y=",
|
||||
"lastModified": 1720957393,
|
||||
"narHash": "sha256-oedh2RwpjEa+TNxhg5Je9Ch6d3W1NKi7DbRO1ziHemA=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "7e7c39ea35c5cdd002cd4588b03a3fb9ece6fad9",
|
||||
"rev": "693bc46d169f5af9c992095736e82c3488bf7dbb",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -582,11 +582,11 @@
|
||||
"treefmt-nix": "treefmt-nix"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1720910388,
|
||||
"narHash": "sha256-gCudumUXHH+o0KFemXecDYySVCzjz7jYDGjdJbrN7gA=",
|
||||
"lastModified": 1721042250,
|
||||
"narHash": "sha256-CEOGzI9WFGezwJ3lok0F//1UEq5crzE2kZDLQK2EtfE=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixvim",
|
||||
"rev": "ac9a1cbf9c7145687e66a1c033d68fc72eca3fd8",
|
||||
"rev": "b9ed90003273f0a75151b32948e16b44891f403c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
15
home/core/gui/ags/README.md
Normal file
15
home/core/gui/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:
|
||||
/home/cnst/.local/share/com.github.Aylur.ags/types
|
||||
45
home/core/gui/ags/config.js
Normal file
45
home/core/gui/ags/config.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import { App, Audio, Notifications, Utils } from "./imports.js";
|
||||
import Bar from "./windows/bar/main.js";
|
||||
import Music from "./windows/music/main.js";
|
||||
import NotificationPopup from "./windows/notifications/popups.js";
|
||||
import Osd from "./windows/osd/main.js";
|
||||
import SystemMenu from "./windows/system-menu/main.js";
|
||||
|
||||
const scss = App.configDir + "/style.scss";
|
||||
const css = App.configDir + "/style.css";
|
||||
|
||||
Utils.exec(`sass ${scss} ${css}`);
|
||||
|
||||
App.connect("config-parsed", () => print("config parsed"));
|
||||
|
||||
App.config({
|
||||
style: css,
|
||||
closeWindowDelay: {
|
||||
"system-menu": 200,
|
||||
},
|
||||
});
|
||||
|
||||
Notifications.popupTimeout = 5000;
|
||||
Notifications.forceTimeout = false;
|
||||
Notifications.cacheActions = true;
|
||||
Audio.maxStreamVolume = 1;
|
||||
|
||||
function reloadCss() {
|
||||
console.log("scss change detected");
|
||||
Utils.exec(`sass ${scss} ${css}`);
|
||||
App.resetCss();
|
||||
App.applyCss(css);
|
||||
}
|
||||
|
||||
Utils.monitorFile(`${App.configDir}/style`, reloadCss);
|
||||
|
||||
/**
|
||||
* @param {import("types/widgets/window.js").Window[]} windows
|
||||
*/
|
||||
function addWindows(windows) {
|
||||
windows.forEach((win) => App.addWindow(win));
|
||||
}
|
||||
|
||||
addWindows([Bar(), Music(), Osd(), SystemMenu(), NotificationPopup()]);
|
||||
|
||||
export {};
|
||||
@@ -1,22 +1,51 @@
|
||||
{
|
||||
inputs,
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}: {
|
||||
# add the home manager module
|
||||
imports = [inputs.ags.homeManagerModules.default];
|
||||
}: let
|
||||
requiredDeps = with pkgs; [
|
||||
bash
|
||||
coreutils
|
||||
dart-sass
|
||||
gawk
|
||||
imagemagick
|
||||
procps
|
||||
ripgrep
|
||||
util-linux
|
||||
];
|
||||
|
||||
programs.ags = {
|
||||
enable = true;
|
||||
guiDeps = with pkgs; [
|
||||
gnome.gnome-control-center
|
||||
mission-center
|
||||
overskride
|
||||
wlogout
|
||||
];
|
||||
|
||||
# null or path, leave as null if you don't want hm to manage the config
|
||||
configDir = null;
|
||||
dependencies = requiredDeps ++ guiDeps;
|
||||
|
||||
# additional packages to add to gjs's runtime
|
||||
extraPackages = with pkgs; [
|
||||
gtksourceview
|
||||
webkitgtk
|
||||
accountsservice
|
||||
];
|
||||
cfg = config.programs.ags;
|
||||
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=/run/wrappers/bin:${lib.makeBinPath dependencies}";
|
||||
ExecStart = "${cfg.package}/bin/ags";
|
||||
Restart = "on-failure";
|
||||
};
|
||||
Install.WantedBy = ["graphical-session.target"];
|
||||
};
|
||||
}
|
||||
|
||||
5
home/core/gui/ags/dprint.json
Normal file
5
home/core/gui/ags/dprint.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"typescript": {},
|
||||
"excludes": ["**/node_modules"],
|
||||
"plugins": ["https://plugins.dprint.dev/typescript-0.88.10.wasm"]
|
||||
}
|
||||
37
home/core/gui/ags/imports.js
Normal file
37
home/core/gui/ags/imports.js
Normal file
@@ -0,0 +1,37 @@
|
||||
// Required components
|
||||
import GLib from "gi://GLib";
|
||||
import App from "resource:///com/github/Aylur/ags/app.js";
|
||||
import Service from "resource:///com/github/Aylur/ags/service.js";
|
||||
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
|
||||
import Variable from "resource:///com/github/Aylur/ags/variable.js";
|
||||
import Widget from "resource:///com/github/Aylur/ags/widget.js";
|
||||
|
||||
// Services
|
||||
import Audio from "resource:///com/github/Aylur/ags/service/audio.js";
|
||||
import Battery from "resource:///com/github/Aylur/ags/service/battery.js";
|
||||
import Bluetooth from "resource:///com/github/Aylur/ags/service/bluetooth.js";
|
||||
import Hyprland from "resource:///com/github/Aylur/ags/service/hyprland.js";
|
||||
import Mpris from "resource:///com/github/Aylur/ags/service/mpris.js";
|
||||
import Network from "resource:///com/github/Aylur/ags/service/network.js";
|
||||
import Notifications from "resource:///com/github/Aylur/ags/service/notifications.js";
|
||||
import SystemTray from "resource:///com/github/Aylur/ags/service/systemtray.js";
|
||||
|
||||
import Icons from "./utils/icons.js";
|
||||
|
||||
export {
|
||||
App,
|
||||
Audio,
|
||||
Battery,
|
||||
Bluetooth,
|
||||
GLib,
|
||||
Hyprland,
|
||||
Icons,
|
||||
Mpris,
|
||||
Network,
|
||||
Notifications,
|
||||
Service,
|
||||
SystemTray,
|
||||
Utils,
|
||||
Variable,
|
||||
Widget,
|
||||
};
|
||||
79
home/core/gui/ags/services/brightness.js
Normal file
79
home/core/gui/ags/services/brightness.js
Normal file
@@ -0,0 +1,79 @@
|
||||
import { Service, Utils } from "../imports.js";
|
||||
import Gio from "gi://Gio";
|
||||
import GLib from "gi://GLib";
|
||||
|
||||
const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
|
||||
|
||||
class BrightnessService extends Service {
|
||||
static {
|
||||
Service.register(
|
||||
this,
|
||||
{ "screen-changed": ["float"] },
|
||||
{ "screen-value": ["float", "rw"] },
|
||||
);
|
||||
}
|
||||
|
||||
#screenValue = 0;
|
||||
|
||||
#interface = Utils.exec("sh -c 'ls -w1 /sys/class/backlight | head -1'");
|
||||
#path = `/sys/class/backlight/${this.#interface}`;
|
||||
#brightness = `${this.#path}/brightness`;
|
||||
|
||||
#max = Number(Utils.readFile(`${this.#path}/max_brightness`));
|
||||
|
||||
get screen_value() {
|
||||
return this.#screenValue;
|
||||
}
|
||||
|
||||
set screen_value(percent) {
|
||||
percent = clamp(percent, 0, 1);
|
||||
this.#screenValue = percent;
|
||||
|
||||
const file = Gio.File.new_for_path(this.#brightness);
|
||||
const string = `${Math.round(percent * this.#max)}`;
|
||||
|
||||
new Promise((resolve, _) => {
|
||||
file.replace_contents_bytes_async(
|
||||
new GLib.Bytes(new TextEncoder().encode(string)),
|
||||
null,
|
||||
false,
|
||||
Gio.FileCreateFlags.NONE,
|
||||
null,
|
||||
(self, res) => {
|
||||
try {
|
||||
self.replace_contents_finish(res);
|
||||
resolve(self);
|
||||
} catch (error) {
|
||||
print(error);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.#updateScreenValue();
|
||||
Utils.monitorFile(this.#brightness, () => this.#onChange());
|
||||
}
|
||||
|
||||
#updateScreenValue() {
|
||||
this.#screenValue = Number(Utils.readFile(this.#brightness)) / this.#max;
|
||||
}
|
||||
|
||||
#onChange() {
|
||||
this.#updateScreenValue();
|
||||
|
||||
this.notify("screen-value");
|
||||
this.emit("screen-changed", this.#screenValue);
|
||||
}
|
||||
|
||||
connectWidget(widget, callback, event = "screen-changed") {
|
||||
super.connectWidget(widget, callback, event);
|
||||
}
|
||||
}
|
||||
|
||||
const service = new BrightnessService();
|
||||
|
||||
export default service;
|
||||
65
home/core/gui/ags/services/osd.js
Normal file
65
home/core/gui/ags/services/osd.js
Normal file
@@ -0,0 +1,65 @@
|
||||
import { Audio, Icons, Service, Utils } from "../imports.js";
|
||||
import { audioIcon, micIcon } from "../utils/audio.js";
|
||||
import Brightness from "./brightness.js";
|
||||
|
||||
class Indicator extends Service {
|
||||
static {
|
||||
Service.register(this, {
|
||||
popup: ["jsobject", "boolean"],
|
||||
});
|
||||
}
|
||||
|
||||
#delay = 1500;
|
||||
#count = 0;
|
||||
|
||||
popup(value, label, icon, showProgress = true) {
|
||||
const props = {
|
||||
value,
|
||||
label,
|
||||
icon,
|
||||
showProgress,
|
||||
};
|
||||
this.emit("popup", props, true);
|
||||
this.#count++;
|
||||
Utils.timeout(this.#delay, () => {
|
||||
this.#count--;
|
||||
|
||||
if (this.#count === 0) {
|
||||
this.emit("popup", props, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bluetooth(addr) {
|
||||
this.popup(0, getBluetoothDevice(addr), Icons.bluetooth.active, false);
|
||||
}
|
||||
|
||||
speaker() {
|
||||
this.popup(
|
||||
Audio.speaker?.volume ?? 0,
|
||||
Audio.speaker?.description ?? "",
|
||||
audioIcon(),
|
||||
);
|
||||
}
|
||||
|
||||
mic() {
|
||||
this.popup(
|
||||
Audio.microphone?.volume || 0,
|
||||
Audio.microphone?.description || "",
|
||||
micIcon(),
|
||||
);
|
||||
}
|
||||
|
||||
display() {
|
||||
// brightness is async, so lets wait a bit
|
||||
Utils.timeout(10, () =>
|
||||
this.popup(Brightness.screenValue, "Brightness", Icons.brightness),
|
||||
);
|
||||
}
|
||||
|
||||
connect(event = "popup", callback) {
|
||||
return super.connect(event, callback);
|
||||
}
|
||||
}
|
||||
|
||||
export default new Indicator();
|
||||
18
home/core/gui/ags/style.css
Normal file
18
home/core/gui/ags/style.css
Normal file
@@ -0,0 +1,18 @@
|
||||
/* Error: expected "{".
|
||||
* ,
|
||||
* 1 | colors-dark.scss
|
||||
* | ^
|
||||
* '
|
||||
* /home/cnst/.config/ags/style/colors.scss 1:17 @import
|
||||
* /home/cnst/.config/ags/style.scss 5:9 root stylesheet */
|
||||
|
||||
body::before {
|
||||
font-family: "Source Code Pro", "SF Mono", Monaco, Inconsolata, "Fira Mono",
|
||||
"Droid Sans Mono", monospace, monospace;
|
||||
white-space: pre;
|
||||
display: block;
|
||||
padding: 1em;
|
||||
margin-bottom: 1em;
|
||||
border-bottom: 2px solid black;
|
||||
content: 'Error: expected "{".\a \2577 \a 1 \2502 colors-dark.scss\a \2502 ^\a \2575 \a /home/cnst/.config/ags/style/colors.scss 1:17 @import\a /home/cnst/.config/ags/style.scss 5:9 root stylesheet';
|
||||
}
|
||||
13
home/core/gui/ags/style.scss
Normal file
13
home/core/gui/ags/style.scss
Normal file
@@ -0,0 +1,13 @@
|
||||
/* style aggregator */
|
||||
|
||||
/* setup */
|
||||
@import "style/prelude";
|
||||
@import "style/colors";
|
||||
@import "style/general";
|
||||
|
||||
/* modules & windows */
|
||||
@import "style/bar";
|
||||
@import "style/music";
|
||||
@import "style/osd";
|
||||
@import "style/system-menu";
|
||||
@import "style/notifications";
|
||||
134
home/core/gui/ags/style/bar.scss
Normal file
134
home/core/gui/ags/style/bar.scss
Normal file
@@ -0,0 +1,134 @@
|
||||
.bar {
|
||||
background: $bar-bg;
|
||||
min-height: 32px;
|
||||
|
||||
.module {
|
||||
margin: 0 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* workspaces */
|
||||
.bar .workspaces {
|
||||
margin: 0.2rem 0.5rem;
|
||||
|
||||
button {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 2rem;
|
||||
margin: 0.7rem 0.2rem;
|
||||
min-width: 1rem;
|
||||
transition: 100ms linear;
|
||||
}
|
||||
|
||||
.focused {
|
||||
min-width: 2rem;
|
||||
}
|
||||
|
||||
.monitor0 {
|
||||
background: $red;
|
||||
}
|
||||
|
||||
.monitor1 {
|
||||
background: $yellow;
|
||||
}
|
||||
|
||||
.monitor2 {
|
||||
background: $green;
|
||||
}
|
||||
|
||||
.monitor3 {
|
||||
background: $blue;
|
||||
}
|
||||
}
|
||||
|
||||
/* music */
|
||||
.bar .music {
|
||||
&>box {
|
||||
@include animate;
|
||||
border-radius: $round2;
|
||||
margin: 0.4rem;
|
||||
}
|
||||
|
||||
&.active>box {
|
||||
background: $surface;
|
||||
}
|
||||
|
||||
.cover {
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
border-radius: 50%;
|
||||
min-width: 2rem;
|
||||
min-height: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* tray */
|
||||
.tray button {
|
||||
@include button;
|
||||
background: none;
|
||||
margin: 0.5rem 0;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 0.3rem;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: $surface;
|
||||
}
|
||||
}
|
||||
|
||||
menu {
|
||||
background: $tooltip-bg;
|
||||
border-radius: $round;
|
||||
|
||||
separator {
|
||||
background-color: $surface;
|
||||
}
|
||||
|
||||
menuitem {
|
||||
@include button;
|
||||
border-radius: 0;
|
||||
padding: 0.4rem 0.7rem;
|
||||
|
||||
&:first-child {
|
||||
border-radius: $round $round 0 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-radius: 0 0 $round $round;
|
||||
}
|
||||
|
||||
&:only-child {
|
||||
border-radius: $round;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* system-info */
|
||||
.bar .system-info {
|
||||
margin: 0 0.2rem;
|
||||
|
||||
&>box {
|
||||
margin: 0 0.3rem;
|
||||
}
|
||||
|
||||
.type {
|
||||
font-size: 0.55rem;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
.system-menu-toggler {
|
||||
box {
|
||||
@include animate;
|
||||
margin: 0.4rem 0;
|
||||
border-radius: $round2;
|
||||
}
|
||||
|
||||
&.active box {
|
||||
background: $surface;
|
||||
}
|
||||
}
|
||||
32
home/core/gui/ags/style/colors-dark.scss
Normal file
32
home/core/gui/ags/style/colors-dark.scss
Normal file
@@ -0,0 +1,32 @@
|
||||
$red: #f38ba8;
|
||||
$yellow: #f9e2af;
|
||||
$green: #a6e3a1;
|
||||
$blue: #89b4fa;
|
||||
|
||||
$tooltip-bg: #000000;
|
||||
$fg: #ffffff;
|
||||
$bg: rgba(0, 0, 0, 0.3);
|
||||
$bar-bg: rgba(0, 0, 0, 0.21);
|
||||
|
||||
$surface: rgba(255, 255, 255, 0.15);
|
||||
$overlay: rgba(255, 255, 255, 0.7);
|
||||
|
||||
$accent: #9d5b7a;
|
||||
|
||||
/* buttons */
|
||||
$button-enabled: $accent;
|
||||
$button-enabled-hover: adjust_color($button-enabled, $lightness: -10%);
|
||||
|
||||
$button-disabled: $surface;
|
||||
$button-disabled-hover: adjust_color($button-disabled, $alpha: +0.1);
|
||||
|
||||
* {
|
||||
text-shadow: 0 2px 3px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
@mixin border {
|
||||
// border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
box-shadow:
|
||||
// inset 0 0 0 1px rgba(255, 255, 255, 0.1),
|
||||
0 3px 5px 1px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
32
home/core/gui/ags/style/colors-light.scss
Normal file
32
home/core/gui/ags/style/colors-light.scss
Normal file
@@ -0,0 +1,32 @@
|
||||
$red: #f38ba8;
|
||||
$yellow: #f9e2af;
|
||||
$green: #a6e3a1;
|
||||
$blue: #89b4fa;
|
||||
|
||||
$tooltip-bg: #ffffff;
|
||||
$fg: #000000;
|
||||
$bg: rgba(255, 255, 255, 0.5);
|
||||
$bar-bg: rgba(255, 255, 255, 0.3);
|
||||
|
||||
$surface: rgba(255, 255, 255, 0.3);
|
||||
$overlay: rgba(0, 0, 0, 0.5);
|
||||
|
||||
$accent: #ddbaef;
|
||||
|
||||
/* buttons */
|
||||
$button-enabled: $accent;
|
||||
$button-enabled-hover: adjust_color($button-enabled, $lightness: -10%);
|
||||
|
||||
$button-disabled: $surface;
|
||||
$button-disabled-hover: adjust_color($button-disabled, $alpha: +0.1);
|
||||
|
||||
* {
|
||||
text-shadow: 0 2px 3px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
@mixin border {
|
||||
// border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
box-shadow:
|
||||
// inset 0 0 0 1px rgba(255, 255, 255, 0.1),
|
||||
0 3px 5px 1px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
1
home/core/gui/ags/style/colors.scss
Normal file
1
home/core/gui/ags/style/colors.scss
Normal file
@@ -0,0 +1 @@
|
||||
colors-dark.scss
|
||||
100
home/core/gui/ags/style/general.scss
Normal file
100
home/core/gui/ags/style/general.scss
Normal file
@@ -0,0 +1,100 @@
|
||||
/* general styles */
|
||||
|
||||
$round: 8px;
|
||||
$round2: calc($round * 2);
|
||||
$margin: 0.4rem;
|
||||
$padding: 0.4rem;
|
||||
$border-width: 2px;
|
||||
$scale: 0.5rem;
|
||||
|
||||
@mixin animate {
|
||||
transition: 200ms;
|
||||
}
|
||||
|
||||
* {
|
||||
color: $fg;
|
||||
}
|
||||
|
||||
/* mixins */
|
||||
@mixin window-rounding {
|
||||
border-radius: $round2;
|
||||
}
|
||||
|
||||
@mixin rounding {
|
||||
border-radius: calc($round2 - $padding - $border-width);
|
||||
}
|
||||
|
||||
@mixin window-box {
|
||||
@include rounding;
|
||||
|
||||
background: $surface;
|
||||
box-shadow: 0 1px 5px -5px rgba(0, 0, 0, 0.5);
|
||||
margin: $margin;
|
||||
padding: $padding;
|
||||
}
|
||||
|
||||
@mixin window {
|
||||
@include border;
|
||||
@include window-rounding;
|
||||
|
||||
background: $bg;
|
||||
margin: 5px 10px 15px;
|
||||
padding: $padding;
|
||||
}
|
||||
|
||||
tooltip {
|
||||
background: $tooltip-bg;
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(255, 255, 255, 0.1),
|
||||
0 0 rgba(0, 0, 0, 0.4);
|
||||
border-radius: $round;
|
||||
}
|
||||
|
||||
/* scales & progress bars */
|
||||
scale,
|
||||
progressbar {
|
||||
trough {
|
||||
background-color: $surface;
|
||||
border-radius: $scale;
|
||||
min-width: calc($scale * 10);
|
||||
padding: 0 calc($scale / 2);
|
||||
}
|
||||
|
||||
highlight,
|
||||
progress {
|
||||
background: $overlay;
|
||||
border-radius: $scale;
|
||||
margin: 0 calc(0px - $scale / 2);
|
||||
min-height: $scale;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin button-active {
|
||||
@include animate;
|
||||
background: $button-enabled;
|
||||
border-radius: 5rem;
|
||||
padding: 0.4rem;
|
||||
|
||||
&:hover {
|
||||
background: $button-enabled-hover;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin button {
|
||||
@include animate;
|
||||
background: $button-disabled;
|
||||
border-radius: 5rem;
|
||||
padding: 0.4rem;
|
||||
|
||||
&:hover {
|
||||
background: $button-disabled-hover;
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
@include button-active;
|
||||
}
|
||||
|
||||
.button.disabled {
|
||||
@include button;
|
||||
}
|
||||
54
home/core/gui/ags/style/music.scss
Normal file
54
home/core/gui/ags/style/music.scss
Normal file
@@ -0,0 +1,54 @@
|
||||
.music.window {
|
||||
@include window;
|
||||
|
||||
.cover {
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
border-radius: $round;
|
||||
box-shadow: 0 1px 2px -1px $bg;
|
||||
margin: 0.4rem;
|
||||
min-height: 13rem;
|
||||
min-width: 13rem;
|
||||
}
|
||||
}
|
||||
|
||||
.music.window .info {
|
||||
margin: 0.5rem;
|
||||
|
||||
label,
|
||||
scale {
|
||||
margin: 0.3rem 0;
|
||||
}
|
||||
|
||||
label.position,
|
||||
label.length {
|
||||
font-size: 0.8rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
scale {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
min-width: 14rem;
|
||||
}
|
||||
}
|
||||
|
||||
.music.window .controls {
|
||||
button {
|
||||
margin: 0 0.2rem;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.music.window .player-info {
|
||||
margin-bottom: 0;
|
||||
|
||||
.player-icon {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
}
|
||||
57
home/core/gui/ags/style/notifications.scss
Normal file
57
home/core/gui/ags/style/notifications.scss
Normal file
@@ -0,0 +1,57 @@
|
||||
.notification {
|
||||
@include window;
|
||||
margin: 5px 5px 5px 10px;
|
||||
|
||||
min-width: 25rem;
|
||||
|
||||
border-radius: $round2;
|
||||
background-color: $bg;
|
||||
|
||||
&.critical {
|
||||
border: 1px solid red;
|
||||
}
|
||||
}
|
||||
|
||||
.notifications widget:last-child .notification {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.notification .icon {
|
||||
image {
|
||||
font-size: 5rem;
|
||||
margin: 0.5rem;
|
||||
min-height: 5rem;
|
||||
min-width: 5rem;
|
||||
}
|
||||
|
||||
>box {
|
||||
border-radius: $round;
|
||||
margin: 0.5rem;
|
||||
min-height: 5rem;
|
||||
min-width: 5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.notification .actions .action-button {
|
||||
@include window-box;
|
||||
@include animate;
|
||||
padding: 0.5rem 0;
|
||||
|
||||
&:hover {
|
||||
background: $button-disabled-hover;
|
||||
}
|
||||
}
|
||||
|
||||
.notification .text {
|
||||
margin: 0.5rem;
|
||||
|
||||
.title {
|
||||
margin-bottom: 0.2rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.body {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
23
home/core/gui/ags/style/osd.scss
Normal file
23
home/core/gui/ags/style/osd.scss
Normal file
@@ -0,0 +1,23 @@
|
||||
.osd {
|
||||
@include window;
|
||||
padding: 0;
|
||||
margin-bottom: 2rem;
|
||||
|
||||
image {
|
||||
margin-left: 1rem;
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
progressbar trough {
|
||||
border-radius: 16px;
|
||||
background: none;
|
||||
min-width: 12.5rem;
|
||||
min-height: 2.5rem;
|
||||
}
|
||||
|
||||
progressbar progress {
|
||||
border-radius: 0;
|
||||
border-radius: 16px;
|
||||
min-height: 2.5rem;
|
||||
}
|
||||
}
|
||||
5
home/core/gui/ags/style/prelude.scss
Normal file
5
home/core/gui/ags/style/prelude.scss
Normal file
@@ -0,0 +1,5 @@
|
||||
/* get rid of GTK theme's styles and set defaults */
|
||||
* {
|
||||
all: unset;
|
||||
font-family: Inter, Roboto, sans-serif;
|
||||
}
|
||||
77
home/core/gui/ags/style/system-menu.scss
Normal file
77
home/core/gui/ags/style/system-menu.scss
Normal file
@@ -0,0 +1,77 @@
|
||||
/* general */
|
||||
.system-menu {
|
||||
@include window;
|
||||
margin-top: 4px;
|
||||
margin-right: 4px;
|
||||
|
||||
&>box {
|
||||
@include window-box;
|
||||
}
|
||||
}
|
||||
|
||||
/* toggles */
|
||||
.system-menu .toggle {
|
||||
min-width: 20rem;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
|
||||
.button {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* power profiles */
|
||||
.system-menu .power-profiles {
|
||||
padding: 0;
|
||||
|
||||
.current-profile {
|
||||
padding: 0.3rem;
|
||||
}
|
||||
|
||||
image,
|
||||
label {
|
||||
margin: 0.3rem;
|
||||
}
|
||||
|
||||
.options {
|
||||
padding: 0;
|
||||
|
||||
widget {
|
||||
@include button;
|
||||
border-radius: 0;
|
||||
|
||||
&:last-child {
|
||||
border-radius: 0 0 $round $round;
|
||||
}
|
||||
|
||||
box {
|
||||
padding: 0.3rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* sliders */
|
||||
.system-menu .sliders {
|
||||
image {
|
||||
margin: 0.3rem;
|
||||
}
|
||||
|
||||
scale {
|
||||
margin: 0 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.system-menu .battery-box {
|
||||
|
||||
image,
|
||||
label {
|
||||
margin: 0 0.3rem;
|
||||
}
|
||||
|
||||
.time {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
}
|
||||
18
home/core/gui/ags/tsconfig.json
Normal file
18
home/core/gui/ags/tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"lib": [
|
||||
"ES2022"
|
||||
],
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"strict": true,
|
||||
"noImplicitAny": false,
|
||||
"baseUrl": ".",
|
||||
"typeRoots": [
|
||||
"./types"
|
||||
],
|
||||
"skipLibCheck": true
|
||||
}
|
||||
}
|
||||
1
home/core/gui/ags/types
Symbolic link
1
home/core/gui/ags/types
Symbolic link
@@ -0,0 +1 @@
|
||||
/home/cnst/.local/share/com.github.Aylur.ags/types
|
||||
30
home/core/gui/ags/utils/audio.js
Normal file
30
home/core/gui/ags/utils/audio.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Audio, Icons } from "../imports.js";
|
||||
|
||||
export const audioIcon = () => {
|
||||
if (Audio.speaker?.stream.isMuted) return Icons.volume.muted;
|
||||
|
||||
const vol = Audio.speaker?.volume * 100;
|
||||
const icon = [
|
||||
[101, "overamplified"],
|
||||
[67, "high"],
|
||||
[34, "medium"],
|
||||
[1, "low"],
|
||||
[0, "muted"],
|
||||
].find(([threshold]) => threshold <= vol)[1];
|
||||
|
||||
return Icons.volume[icon];
|
||||
};
|
||||
|
||||
export const micIcon = () => {
|
||||
if (Audio.microphone?.stream.isMuted) return Icons.microphone.muted;
|
||||
|
||||
const vol = Audio.microphone?.volume * 100;
|
||||
const icon = [
|
||||
[67, "high"],
|
||||
[34, "medium"],
|
||||
[1, "low"],
|
||||
[0, "muted"],
|
||||
].find(([threshold]) => threshold <= vol)[1];
|
||||
|
||||
return Icons.microphone[icon];
|
||||
};
|
||||
22
home/core/gui/ags/utils/battery.js
Normal file
22
home/core/gui/ags/utils/battery.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Battery } from "../imports.js";
|
||||
|
||||
export const toTime = (time) => {
|
||||
const MINUTE = 60;
|
||||
const HOUR = MINUTE * 60;
|
||||
|
||||
if (time > 24 * HOUR) return "";
|
||||
|
||||
const hours = Math.round(time / HOUR);
|
||||
const minutes = Math.round((time - hours * HOUR) / MINUTE);
|
||||
|
||||
const hoursDisplay = hours > 0 ? `${hours}h ` : "";
|
||||
const minutesDisplay = minutes > 0 ? `${minutes}m ` : "";
|
||||
|
||||
return `${hoursDisplay}${minutesDisplay}`;
|
||||
};
|
||||
|
||||
export const batteryTime = () => {
|
||||
return Battery.timeRemaining > 0 && toTime(Battery.timeRemaining) != ""
|
||||
? `${toTime(Battery.timeRemaining)}remaining`
|
||||
: "";
|
||||
};
|
||||
27
home/core/gui/ags/utils/bluetooth.js
Normal file
27
home/core/gui/ags/utils/bluetooth.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Bluetooth, Icons } from "../imports.js";
|
||||
|
||||
export const getBluetoothDevice = (addr) =>
|
||||
Bluetooth.getDevice(addr).alias ?? Bluetooth.getDevice(addr).name;
|
||||
|
||||
export const getBluetoothIcon = (connected) => {
|
||||
if (!Bluetooth.enabled) return Icons.bluetooth.disabled;
|
||||
if (connected.length > 0) return Icons.bluetooth.active;
|
||||
return Icons.bluetooth.disconnected;
|
||||
};
|
||||
|
||||
export const getBluetoothText = (connected) => {
|
||||
if (!Bluetooth.enabled) return "Bluetooth off";
|
||||
|
||||
if (connected.length > 0) {
|
||||
const dev = Bluetooth.getDevice(connected[0].address);
|
||||
let battery_str = "";
|
||||
|
||||
if (dev.battery_percentage > 0) {
|
||||
battery_str += ` ${dev.battery_percentage}%`;
|
||||
}
|
||||
|
||||
return dev.name + battery_str;
|
||||
}
|
||||
|
||||
return "Bluetooth on";
|
||||
};
|
||||
107
home/core/gui/ags/utils/hyprland.js
Normal file
107
home/core/gui/ags/utils/hyprland.js
Normal file
@@ -0,0 +1,107 @@
|
||||
import { Hyprland } from "../imports.js";
|
||||
|
||||
export let DEFAULT_MONITOR;
|
||||
const connID = Hyprland.connect("notify::workspaces", () => {
|
||||
Hyprland.disconnect(connID);
|
||||
|
||||
DEFAULT_MONITOR = {
|
||||
name: Hyprland.monitors[0].name,
|
||||
id: Hyprland.monitors[0].id,
|
||||
};
|
||||
});
|
||||
|
||||
export const changeWorkspace = (ws) =>
|
||||
Hyprland.messageAsync(`dispatch workspace ${ws}`);
|
||||
|
||||
export const focusedSwitch = (self) => {
|
||||
const id = Hyprland.active.workspace.id;
|
||||
if (self.lastFocused == id) return;
|
||||
|
||||
self.children[self.lastFocused - 1].toggleClassName("focused", false);
|
||||
self.children[id - 1].toggleClassName("focused", true);
|
||||
self.lastFocused = id;
|
||||
};
|
||||
|
||||
export const added = (self, name) => {
|
||||
if (!name) return;
|
||||
const ws = Hyprland.workspaces.find((e) => e.name == name);
|
||||
const id = ws?.id ?? Number(name);
|
||||
const child = self.children[id - 1];
|
||||
|
||||
child.monitor = {
|
||||
name: ws?.monitor ?? DEFAULT_MONITOR.name,
|
||||
id: ws?.monitorID ?? DEFAULT_MONITOR.id,
|
||||
};
|
||||
|
||||
child.active = true;
|
||||
child.toggleClassName(`monitor${child.monitor.id}`, true);
|
||||
|
||||
// if this id is bigger than the last biggest id, visibilise all other ws before it
|
||||
if (id > self.biggestId) {
|
||||
for (let i = self.biggestId; i <= id; i++) {
|
||||
self.children[i - 1].visible = true;
|
||||
}
|
||||
self.biggestId = id;
|
||||
}
|
||||
};
|
||||
|
||||
const makeInvisible = (self, id) => {
|
||||
if (id <= 1) return;
|
||||
|
||||
const child = self.children[id - 1];
|
||||
if (child.active) {
|
||||
self.biggestId = id;
|
||||
return;
|
||||
}
|
||||
|
||||
child.visible = false;
|
||||
makeInvisible(self, id - 1);
|
||||
};
|
||||
|
||||
export const removed = (self, name) => {
|
||||
if (!name) return;
|
||||
|
||||
const id = Number(name);
|
||||
const child = self.children[id - 1];
|
||||
|
||||
child.toggleClassName(`monitor${child.monitor.id}`, false);
|
||||
child.active = false;
|
||||
|
||||
// if this id is the biggest id, unvisibilise it and all other inactives until the next active before it
|
||||
if (id == self.biggestId) {
|
||||
makeInvisible(self, id);
|
||||
}
|
||||
};
|
||||
|
||||
export const moveWorkspace = (self, data) => {
|
||||
const [id, name] = data.split(",");
|
||||
|
||||
const child = self.children[id - 1];
|
||||
|
||||
// remove previous monitor class
|
||||
child.toggleClassName(`monitor${child.monitor.id}`, false);
|
||||
|
||||
// add new monitor and class
|
||||
const monitor = Hyprland.monitors.find((e) => e.name == name);
|
||||
|
||||
child.monitor = {
|
||||
name,
|
||||
id: monitor?.id ?? DEFAULT_MONITOR.id,
|
||||
};
|
||||
|
||||
print(`child ${id}: monitor ${name} ${child.monitor.id}`);
|
||||
child.toggleClassName(`monitor${child.monitor.id}`, true);
|
||||
};
|
||||
|
||||
export const sortWorkspaces = () => {
|
||||
return Hyprland.workspaces
|
||||
.sort((x, y) => {
|
||||
return x.id - y.id;
|
||||
})
|
||||
.filter((x) => {
|
||||
return x.name.indexOf("special") == -1;
|
||||
});
|
||||
};
|
||||
|
||||
export const getLastWorkspaceId = () => sortWorkspaces().slice(-1)[0].id;
|
||||
export const workspaceActive = (id) => sortWorkspaces().some((e) => e.id == id);
|
||||
35
home/core/gui/ags/utils/icons.js
Normal file
35
home/core/gui/ags/utils/icons.js
Normal file
@@ -0,0 +1,35 @@
|
||||
export default {
|
||||
bluetooth: {
|
||||
active: "bluetooth-active-symbolic",
|
||||
disabled: "bluetooth-disabled-symbolic",
|
||||
disconnected: "bluetooth-disconnected-symbolic",
|
||||
},
|
||||
|
||||
brightness: "display-brightness-symbolic",
|
||||
|
||||
media: {
|
||||
play: "media-playback-start-symbolic",
|
||||
pause: "media-playback-pause-symbolic",
|
||||
next: "media-skip-forward-symbolic",
|
||||
previous: "media-skip-backward-symbolic",
|
||||
|
||||
player: "multimedia-player-symbolic",
|
||||
},
|
||||
|
||||
volume: {
|
||||
muted: "audio-volume-muted-symbolic",
|
||||
low: "audio-volume-low-symbolic",
|
||||
medium: "audio-volume-medium-symbolic",
|
||||
high: "audio-volume-high-symbolic",
|
||||
overamplified: "audio-volume-overamplified-symbolic",
|
||||
},
|
||||
|
||||
microphone: {
|
||||
muted: "microphone-sensitivity-muted-symbolic",
|
||||
low: "microphone-sensitivity-low-symbolic",
|
||||
medium: "microphone-sensitivity-medium-symbolic",
|
||||
high: "microphone-sensitivity-high-symbolic",
|
||||
},
|
||||
|
||||
powerButton: "system-shutdown-symbolic",
|
||||
};
|
||||
46
home/core/gui/ags/utils/mpris.js
Normal file
46
home/core/gui/ags/utils/mpris.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Icons, Utils } from "../imports.js";
|
||||
import GLib from "gi://GLib";
|
||||
|
||||
export const findPlayer = (players) => {
|
||||
// try to get the first active player
|
||||
const activePlayer = players.find((p) => p.playBackStatus == "Playing");
|
||||
if (activePlayer != null) return activePlayer;
|
||||
|
||||
// otherwise get the first "working" player
|
||||
for (const p of players) {
|
||||
if (p.title != "undefined") return p;
|
||||
}
|
||||
};
|
||||
|
||||
export const mprisStateIcon = (status) => {
|
||||
const state = status == "Playing" ? "pause" : "play";
|
||||
return Icons.media[state];
|
||||
};
|
||||
|
||||
export const MEDIA_CACHE_PATH = Utils.CACHE_DIR + "/media";
|
||||
export const blurredPath = MEDIA_CACHE_PATH + "/blurred";
|
||||
|
||||
export const generateBackground = (cover_path) => {
|
||||
const url = cover_path;
|
||||
if (!url) return "";
|
||||
|
||||
const makeBg = (bg) => `background: center/cover url('${bg}')`;
|
||||
|
||||
const blurred = blurredPath + url.substring(MEDIA_CACHE_PATH.length);
|
||||
|
||||
if (GLib.file_test(blurred, GLib.FileTest.EXISTS)) {
|
||||
return makeBg(blurred);
|
||||
}
|
||||
|
||||
Utils.ensureDirectory(blurredPath);
|
||||
Utils.exec(`convert ${url} -blur 0x22 ${blurred}`);
|
||||
|
||||
return makeBg(blurred);
|
||||
};
|
||||
|
||||
export function lengthStr(length) {
|
||||
const min = Math.floor(length / 60);
|
||||
const sec = Math.floor(length % 60);
|
||||
const sec0 = sec < 10 ? "0" : "";
|
||||
return `${min}:${sec0}${sec}`;
|
||||
}
|
||||
27
home/core/gui/ags/utils/net.js
Normal file
27
home/core/gui/ags/utils/net.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Network } from "../imports.js";
|
||||
|
||||
export const getNetIcon = () => {
|
||||
if (Network.connectivity == "none") return "";
|
||||
if (Network.primary == "wired") return "network-wired";
|
||||
|
||||
return Network.wifi.icon_name;
|
||||
};
|
||||
|
||||
export const getNetText = () => {
|
||||
// no connection
|
||||
if (Network.connectivity == "none") return "No connection";
|
||||
|
||||
// wired
|
||||
if (Network.primary == "wired") return "Wired";
|
||||
|
||||
// wifi
|
||||
const wifi = Network.wifi;
|
||||
switch (wifi.internet) {
|
||||
case "connected":
|
||||
return wifi.ssid;
|
||||
case "connecting":
|
||||
return "Connecting";
|
||||
case "disconnected":
|
||||
return "Disconnected";
|
||||
}
|
||||
};
|
||||
46
home/core/gui/ags/utils/popup_window.js
Normal file
46
home/core/gui/ags/utils/popup_window.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import App from "resource:///com/github/Aylur/ags/app.js";
|
||||
import { Widget } from "../imports.js";
|
||||
const { Box, Revealer, Window } = Widget;
|
||||
|
||||
export default ({
|
||||
name,
|
||||
child,
|
||||
revealerSetup = null,
|
||||
transition = "crossfade",
|
||||
transitionDuration = 200,
|
||||
...props
|
||||
}) => {
|
||||
const window = Window({
|
||||
name,
|
||||
popup: false,
|
||||
focusable: false,
|
||||
visible: false,
|
||||
...props,
|
||||
|
||||
setup: (self) => (self.getChild = () => child),
|
||||
|
||||
child: Box({
|
||||
css: `
|
||||
min-height: 1px;
|
||||
min-width: 1px;
|
||||
padding: 1px;
|
||||
`,
|
||||
child: Revealer({
|
||||
transition,
|
||||
transitionDuration,
|
||||
child: child,
|
||||
|
||||
setup:
|
||||
revealerSetup ??
|
||||
((self) =>
|
||||
self.hook(App, (self, currentName, visible) => {
|
||||
if (currentName === name) {
|
||||
self.reveal_child = visible;
|
||||
}
|
||||
})),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
return window;
|
||||
};
|
||||
62
home/core/gui/ags/windows/bar/main.js
Normal file
62
home/core/gui/ags/windows/bar/main.js
Normal file
@@ -0,0 +1,62 @@
|
||||
import { App, Widget } from "../../imports.js";
|
||||
import Battery from "./modules/battery.js";
|
||||
import Bluetooth from "./modules/bluetooth.js";
|
||||
import Date from "./modules/date.js";
|
||||
import Music from "./modules/music.js";
|
||||
import Net from "./modules/net.js";
|
||||
import CpuRam from "./modules/cpu_ram.js";
|
||||
import Tray from "./modules/tray.js";
|
||||
import Workspaces from "./modules/workspaces.js";
|
||||
|
||||
const SystemInfo = () =>
|
||||
Widget.EventBox({
|
||||
className: "system-menu-toggler",
|
||||
onPrimaryClick: () => App.toggleWindow("system-menu"),
|
||||
|
||||
child: Widget.Box({
|
||||
children: [Net(), Bluetooth(), Battery()],
|
||||
}),
|
||||
}).hook(App, (self, window, visible) => {
|
||||
if (window === "system-menu") {
|
||||
self.toggleClassName("active", visible);
|
||||
}
|
||||
});
|
||||
|
||||
const Start = () =>
|
||||
Widget.Box({
|
||||
hexpand: true,
|
||||
hpack: "start",
|
||||
children: [
|
||||
Workspaces(),
|
||||
// Indicators
|
||||
],
|
||||
});
|
||||
|
||||
const Center = () =>
|
||||
Widget.Box({
|
||||
children: [Music()],
|
||||
});
|
||||
|
||||
const End = () =>
|
||||
Widget.Box({
|
||||
hexpand: true,
|
||||
hpack: "end",
|
||||
|
||||
children: [Tray(), CpuRam(), SystemInfo(), Date()],
|
||||
});
|
||||
|
||||
export default () =>
|
||||
Widget.Window({
|
||||
monitor: 0,
|
||||
name: `bar`,
|
||||
anchor: ["bottom", "left", "right"],
|
||||
exclusivity: "exclusive",
|
||||
|
||||
child: Widget.CenterBox({
|
||||
className: "bar",
|
||||
|
||||
startWidget: Start(),
|
||||
centerWidget: Center(),
|
||||
endWidget: End(),
|
||||
}),
|
||||
});
|
||||
6
home/core/gui/ags/windows/bar/modules/battery.js
Normal file
6
home/core/gui/ags/windows/bar/modules/battery.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import { Battery, Widget } from "../../../imports.js";
|
||||
|
||||
export default () =>
|
||||
Widget.Icon({ className: "battery module" })
|
||||
.bind("icon", Battery, "icon-name")
|
||||
.bind("tooltip-text", Battery, "percent", (p) => `Battery on ${p}%`);
|
||||
10
home/core/gui/ags/windows/bar/modules/bluetooth.js
Normal file
10
home/core/gui/ags/windows/bar/modules/bluetooth.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Bluetooth, Widget } from "../../../imports.js";
|
||||
import {
|
||||
getBluetoothIcon,
|
||||
getBluetoothText,
|
||||
} from "../../../utils/bluetooth.js";
|
||||
|
||||
export default () =>
|
||||
Widget.Icon({ className: "bluetooth module" })
|
||||
.bind("icon", Bluetooth, "connected-devices", getBluetoothIcon)
|
||||
.bind("tooltip-text", Bluetooth, "connected-devices", getBluetoothText);
|
||||
70
home/core/gui/ags/windows/bar/modules/cpu_ram.js
Normal file
70
home/core/gui/ags/windows/bar/modules/cpu_ram.js
Normal file
@@ -0,0 +1,70 @@
|
||||
import { Utils, Widget } from "../../../imports.js";
|
||||
|
||||
const Indicator = (props) =>
|
||||
Widget.Box({
|
||||
vertical: true,
|
||||
vexpand: true,
|
||||
vpack: "center",
|
||||
|
||||
children: [
|
||||
Widget.Label({
|
||||
className: "type",
|
||||
label: props.type,
|
||||
}),
|
||||
Widget.Label({ className: "value" }).poll(2000, props.poll),
|
||||
],
|
||||
}).poll(2000, props.boxpoll);
|
||||
|
||||
const cpu = {
|
||||
type: "CPU",
|
||||
|
||||
poll: (self) =>
|
||||
Utils.execAsync([
|
||||
"sh",
|
||||
"-c",
|
||||
`top -bn1 | rg '%Cpu' | tail -1 | awk '{print 100-$8}'`,
|
||||
])
|
||||
.then((r) => (self.label = Math.round(Number(r)) + "%"))
|
||||
.catch((err) => print(err)),
|
||||
|
||||
boxpoll: (self) =>
|
||||
Utils.execAsync(["sh", "-c", "lscpu --parse=MHZ"])
|
||||
.then((r) => {
|
||||
const mhz = r.split("\n").slice(4);
|
||||
const freq = mhz.reduce((acc, e) => acc + Number(e), 0) / mhz.length;
|
||||
self.tooltipText = Math.round(freq) + " MHz";
|
||||
})
|
||||
.catch((err) => print(err)),
|
||||
};
|
||||
|
||||
const ram = {
|
||||
type: "MEM",
|
||||
poll: (self) =>
|
||||
Utils.execAsync([
|
||||
"sh",
|
||||
"-c",
|
||||
`free | tail -2 | head -1 | awk '{print $3/$2*100}'`,
|
||||
])
|
||||
.then((r) => (self.label = Math.round(Number(r)) + "%"))
|
||||
.catch((err) => print(err)),
|
||||
|
||||
boxpoll: (self) =>
|
||||
Utils.execAsync([
|
||||
"sh",
|
||||
"-c",
|
||||
"free --si -h | tail -2 | head -1 | awk '{print $3}'",
|
||||
])
|
||||
.then((r) => (self.tooltipText = r))
|
||||
.catch((err) => print(err)),
|
||||
};
|
||||
|
||||
export default () =>
|
||||
Widget.EventBox({
|
||||
onPrimaryClick: () => Utils.execAsync(["missioncenter"]),
|
||||
|
||||
child: Widget.Box({
|
||||
className: "system-info module",
|
||||
|
||||
children: [Indicator(cpu), Indicator(ram)],
|
||||
}),
|
||||
});
|
||||
10
home/core/gui/ags/windows/bar/modules/date.js
Normal file
10
home/core/gui/ags/windows/bar/modules/date.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Utils, Widget } from "../../../imports.js";
|
||||
|
||||
export default () =>
|
||||
Widget.EventBox({
|
||||
child: Widget.Label({ className: "date module" }).poll(1000, (self) =>
|
||||
Utils.execAsync(["date", "+%a %b %d %H:%M"]).then(
|
||||
(r) => (self.label = r),
|
||||
),
|
||||
),
|
||||
});
|
||||
40
home/core/gui/ags/windows/bar/modules/music.js
Normal file
40
home/core/gui/ags/windows/bar/modules/music.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import { Mpris, Widget } from "../../../imports.js";
|
||||
import App from "resource:///com/github/Aylur/ags/app.js";
|
||||
import { findPlayer } from "../../../utils/mpris.js";
|
||||
|
||||
const Cover = (player) =>
|
||||
Widget.Box({ className: "cover" }).bind(
|
||||
"css",
|
||||
player,
|
||||
"cover-path",
|
||||
(cover) => `background-image: url('${cover ?? ""}');`,
|
||||
);
|
||||
|
||||
const Title = (player) =>
|
||||
Widget.Label({ className: "title module" }).bind(
|
||||
"label",
|
||||
player,
|
||||
"track-title",
|
||||
(title) => ((title ?? "") == "Unknown title" ? "" : title),
|
||||
);
|
||||
|
||||
export const MusicBox = (player) =>
|
||||
Widget.Box({
|
||||
children: [Cover(player), Title(player)],
|
||||
});
|
||||
|
||||
export default () =>
|
||||
Widget.EventBox({
|
||||
className: "music",
|
||||
onPrimaryClick: () => App.toggleWindow("music"),
|
||||
})
|
||||
.hook(App, (self, window, visible) => {
|
||||
if (window === "music") {
|
||||
self.toggleClassName("active", visible);
|
||||
}
|
||||
})
|
||||
.bind("visible", Mpris, "players", (p) => p.length > 0)
|
||||
.bind("child", Mpris, "players", (players) => {
|
||||
if (players.length == 0) return Widget.Box();
|
||||
return MusicBox(findPlayer(players));
|
||||
});
|
||||
7
home/core/gui/ags/windows/bar/modules/net.js
Normal file
7
home/core/gui/ags/windows/bar/modules/net.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Network, Widget } from "../../../imports.js";
|
||||
import { getNetIcon, getNetText } from "../../../utils/net.js";
|
||||
|
||||
export default () =>
|
||||
Widget.Icon({ className: "net module" })
|
||||
.bind("icon", Network, "connectivity", getNetIcon)
|
||||
.bind("tooltip-text", Network, "connectivity", getNetText);
|
||||
47
home/core/gui/ags/windows/bar/modules/tray.js
Normal file
47
home/core/gui/ags/windows/bar/modules/tray.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import { SystemTray, Widget } from "../../../imports.js";
|
||||
import Gdk from "gi://Gdk?version=3.0";
|
||||
|
||||
const Item = (item) =>
|
||||
Widget.Button({
|
||||
child: Widget.Icon().bind("icon", item, "icon"),
|
||||
|
||||
onPrimaryClick: (_, ev) => {
|
||||
try {
|
||||
item.activate(ev);
|
||||
} catch (err) {
|
||||
print(err);
|
||||
}
|
||||
},
|
||||
|
||||
setup: (self) => {
|
||||
const id = item.menu?.connect("popped-up", (menu) => {
|
||||
self.toggleClassName("active");
|
||||
menu.connect("notify::visible", (menu) => {
|
||||
self.toggleClassName("active", menu.visible);
|
||||
});
|
||||
menu.disconnect(id);
|
||||
});
|
||||
|
||||
if (id) {
|
||||
self.connect("destroy", () => item.menu?.disconnect(id));
|
||||
}
|
||||
|
||||
self.bind("tooltip-markup", item, "tooltip-markup");
|
||||
},
|
||||
|
||||
onSecondaryClick: (btn) =>
|
||||
item.menu?.popup_at_widget(
|
||||
btn,
|
||||
Gdk.Gravity.SOUTH,
|
||||
Gdk.Gravity.NORTH,
|
||||
null,
|
||||
),
|
||||
});
|
||||
|
||||
export default () =>
|
||||
Widget.Box({ className: "tray module" }).bind(
|
||||
"children",
|
||||
SystemTray,
|
||||
"items",
|
||||
(items) => items.map(Item),
|
||||
);
|
||||
72
home/core/gui/ags/windows/bar/modules/workspaces.js
Normal file
72
home/core/gui/ags/windows/bar/modules/workspaces.js
Normal file
@@ -0,0 +1,72 @@
|
||||
import { Hyprland, Widget } from "../../../imports.js";
|
||||
import {
|
||||
added,
|
||||
changeWorkspace,
|
||||
DEFAULT_MONITOR,
|
||||
focusedSwitch,
|
||||
getLastWorkspaceId,
|
||||
moveWorkspace,
|
||||
removed,
|
||||
workspaceActive,
|
||||
} from "../../../utils/hyprland.js";
|
||||
|
||||
globalThis.hyprland = Hyprland;
|
||||
|
||||
const makeWorkspaces = () =>
|
||||
[...Array(10)].map((_, i) => {
|
||||
const id = i + 1;
|
||||
|
||||
return Widget.Button({
|
||||
onPrimaryClick: () => changeWorkspace(id),
|
||||
|
||||
visible: getLastWorkspaceId() >= id,
|
||||
|
||||
setup: (self) => {
|
||||
const ws = Hyprland.getWorkspace(id);
|
||||
self.id = id;
|
||||
self.active = workspaceActive(id);
|
||||
self.monitor = DEFAULT_MONITOR;
|
||||
|
||||
if (self.active) {
|
||||
self.monitor = {
|
||||
name: ws?.monitor ?? DEFAULT_MONITOR.name,
|
||||
id: ws?.monitorID ?? DEFAULT_MONITOR.id,
|
||||
};
|
||||
self.toggleClassName(`monitor${self.monitor.id}`, true);
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
export default () =>
|
||||
Widget.EventBox({
|
||||
onScrollUp: () => changeWorkspace("+1"),
|
||||
onScrollDown: () => changeWorkspace("-1"),
|
||||
|
||||
child: Widget.Box({
|
||||
className: "workspaces module",
|
||||
|
||||
// The Hyprland service is ready later than ags is done parsing the config,
|
||||
// so only build the widget when we receive a signal from it.
|
||||
setup: (self) => {
|
||||
const connID = Hyprland.connect("notify::workspaces", () => {
|
||||
Hyprland.disconnect(connID);
|
||||
|
||||
self.children = makeWorkspaces();
|
||||
self.lastFocused = Hyprland.active.workspace.id;
|
||||
self.biggestId = getLastWorkspaceId();
|
||||
self
|
||||
.hook(Hyprland.active.workspace, focusedSwitch)
|
||||
.hook(Hyprland, added, "workspace-added")
|
||||
.hook(Hyprland, removed, "workspace-removed")
|
||||
.hook(
|
||||
Hyprland,
|
||||
(self, name, data) => {
|
||||
if (name === "moveworkspace") moveWorkspace(self, data);
|
||||
},
|
||||
"event",
|
||||
);
|
||||
});
|
||||
},
|
||||
}),
|
||||
});
|
||||
29
home/core/gui/ags/windows/music/controls.js
vendored
Normal file
29
home/core/gui/ags/windows/music/controls.js
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Icons, Widget } from "../../imports.js";
|
||||
import { mprisStateIcon } from "../../utils/mpris.js";
|
||||
|
||||
export default (player) =>
|
||||
Widget.CenterBox({
|
||||
className: "controls",
|
||||
hpack: "center",
|
||||
|
||||
startWidget: Widget.Button({
|
||||
onClicked: () => player.previous(),
|
||||
child: Widget.Icon(Icons.media.previous),
|
||||
}),
|
||||
|
||||
centerWidget: Widget.Button({
|
||||
onClicked: () => player.playPause(),
|
||||
|
||||
child: Widget.Icon().bind(
|
||||
"icon",
|
||||
player,
|
||||
"play-back-status",
|
||||
mprisStateIcon,
|
||||
),
|
||||
}),
|
||||
|
||||
endWidget: Widget.Button({
|
||||
onClicked: () => player.next(),
|
||||
child: Widget.Icon(Icons.media.next),
|
||||
}),
|
||||
});
|
||||
9
home/core/gui/ags/windows/music/cover.js
Normal file
9
home/core/gui/ags/windows/music/cover.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Widget } from "../../imports.js";
|
||||
|
||||
export default (player) =>
|
||||
Widget.Box({ className: "cover" }).bind(
|
||||
"css",
|
||||
player,
|
||||
"cover-path",
|
||||
(cover) => `background-image: url('${cover ?? ""}')`,
|
||||
);
|
||||
43
home/core/gui/ags/windows/music/main.js
Normal file
43
home/core/gui/ags/windows/music/main.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import { Mpris, Widget } from "../../imports.js";
|
||||
import { findPlayer, generateBackground } from "../../utils/mpris.js";
|
||||
import PopupWindow from "../../utils/popup_window.js";
|
||||
|
||||
import Cover from "./cover.js";
|
||||
import { Artists, Title } from "./title_artists.js";
|
||||
import TimeInfo from "./time_info.js";
|
||||
import Controls from "./controls.js";
|
||||
import PlayerInfo from "./player_info.js";
|
||||
|
||||
const Info = (player) =>
|
||||
Widget.Box({
|
||||
className: "info",
|
||||
vertical: true,
|
||||
vexpand: false,
|
||||
hexpand: false,
|
||||
homogeneous: true,
|
||||
|
||||
children: [
|
||||
PlayerInfo(player),
|
||||
Title(player),
|
||||
Artists(player),
|
||||
Controls(player),
|
||||
TimeInfo(player),
|
||||
],
|
||||
});
|
||||
|
||||
const MusicBox = (player) =>
|
||||
Widget.Box({
|
||||
className: "music window",
|
||||
children: [Cover(player), Info(player)],
|
||||
}).bind("css", player, "cover-path", generateBackground);
|
||||
|
||||
export default () =>
|
||||
PopupWindow({
|
||||
monitor: 0,
|
||||
anchor: ["top"],
|
||||
name: "music",
|
||||
child: Widget.Box(),
|
||||
}).bind("child", Mpris, "players", (players) => {
|
||||
if (players.length == 0) return Widget.Box();
|
||||
return MusicBox(findPlayer(players));
|
||||
});
|
||||
21
home/core/gui/ags/windows/music/player_info.js
Normal file
21
home/core/gui/ags/windows/music/player_info.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Icons, Utils, Widget } from "../../imports.js";
|
||||
|
||||
export default (player) =>
|
||||
Widget.Box({
|
||||
className: "player-info",
|
||||
vexpand: true,
|
||||
vpack: "start",
|
||||
|
||||
children: [
|
||||
Widget.Icon({
|
||||
hexpand: true,
|
||||
hpack: "end",
|
||||
className: "player-icon",
|
||||
tooltipText: player.identity ?? "",
|
||||
}).bind("icon", player, "entry", (entry) => {
|
||||
// the Spotify icon is called spotify-client
|
||||
if (entry == "spotify") entry = "spotify-client";
|
||||
return Utils.lookUpIcon(entry ?? "") ? entry : Icons.media.player;
|
||||
}),
|
||||
],
|
||||
});
|
||||
68
home/core/gui/ags/windows/music/time_info.js
Normal file
68
home/core/gui/ags/windows/music/time_info.js
Normal file
@@ -0,0 +1,68 @@
|
||||
import { Widget } from "../../imports.js";
|
||||
import { lengthStr } from "../../utils/mpris.js";
|
||||
|
||||
export const PositionLabel = (player) =>
|
||||
Widget.Label({
|
||||
className: "position",
|
||||
hexpand: true,
|
||||
xalign: 0,
|
||||
|
||||
setup: (self) => {
|
||||
const update = (_, time) => {
|
||||
player.length > 0
|
||||
? (self.label = lengthStr(time || player.position))
|
||||
: (self.visible = !!player);
|
||||
};
|
||||
|
||||
self.hook(player, update, "position").poll(1000, update);
|
||||
},
|
||||
});
|
||||
|
||||
export const LengthLabel = (player) =>
|
||||
Widget.Label({
|
||||
className: "length",
|
||||
hexpand: true,
|
||||
xalign: 1,
|
||||
})
|
||||
.bind("visible", player, "length", (length) => length > 0)
|
||||
.bind("label", player, "length", (length) => lengthStr(length));
|
||||
|
||||
export const Position = (player) =>
|
||||
Widget.Slider({
|
||||
className: "position",
|
||||
draw_value: false,
|
||||
|
||||
onChange: ({ value }) => (player.position = player.length * value),
|
||||
|
||||
setup: (self) => {
|
||||
const update = () => {
|
||||
if (self.dragging) return;
|
||||
|
||||
self.visible = player.length > 0;
|
||||
|
||||
if (player.length > 0) {
|
||||
self.value = player.position / player.length;
|
||||
}
|
||||
};
|
||||
|
||||
self
|
||||
.hook(player, update)
|
||||
.hook(player, update, "position")
|
||||
.poll(1000, update);
|
||||
},
|
||||
});
|
||||
|
||||
export default (player) =>
|
||||
Widget.Box({
|
||||
vertical: true,
|
||||
vexpand: true,
|
||||
vpack: "end",
|
||||
|
||||
children: [
|
||||
Widget.Box({
|
||||
hexpand: true,
|
||||
children: [PositionLabel(player), LengthLabel(player)],
|
||||
}),
|
||||
Position(player),
|
||||
],
|
||||
});
|
||||
32
home/core/gui/ags/windows/music/title_artists.js
Normal file
32
home/core/gui/ags/windows/music/title_artists.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Widget } from "../../imports.js";
|
||||
|
||||
export const Title = (player) =>
|
||||
Widget.Scrollable({
|
||||
className: "title",
|
||||
vscroll: "never",
|
||||
hscroll: "automatic",
|
||||
|
||||
child: Widget.Label({
|
||||
className: "title",
|
||||
label: "Nothing playing",
|
||||
}).bind(
|
||||
"label",
|
||||
player,
|
||||
"track-title",
|
||||
(title) => title ?? "Nothing playing",
|
||||
),
|
||||
});
|
||||
|
||||
export const Artists = (player) =>
|
||||
Widget.Scrollable({
|
||||
className: "artists",
|
||||
vscroll: "never",
|
||||
hscroll: "automatic",
|
||||
|
||||
child: Widget.Label({ className: "artists" }).bind(
|
||||
"label",
|
||||
player,
|
||||
"track-artists",
|
||||
(artists) => artists.join(", ") ?? "",
|
||||
),
|
||||
});
|
||||
139
home/core/gui/ags/windows/notifications/popups.js
Normal file
139
home/core/gui/ags/windows/notifications/popups.js
Normal file
@@ -0,0 +1,139 @@
|
||||
import { Hyprland, Notifications, Utils, Widget } from "../../imports.js";
|
||||
|
||||
const closeAll = () => {
|
||||
Notifications.popups.map((n) => n.dismiss());
|
||||
};
|
||||
|
||||
/** @param {import("types/service/notifications").Notification} n */
|
||||
const NotificationIcon = ({ app_entry, app_icon, image }) => {
|
||||
if (image) {
|
||||
return Widget.Box({
|
||||
css: `
|
||||
background-image: url("${image}");
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
`,
|
||||
});
|
||||
}
|
||||
|
||||
if (Utils.lookUpIcon(app_icon)) {
|
||||
return Widget.Icon(app_icon);
|
||||
}
|
||||
|
||||
if (app_entry && Utils.lookUpIcon(app_entry)) {
|
||||
return Widget.Icon(app_entry);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
/** @param {import('types/service/notifications').Notification} n */
|
||||
export const Notification = (n) => {
|
||||
const icon = Widget.Box({
|
||||
vpack: "start",
|
||||
class_name: "icon",
|
||||
// @ts-ignore
|
||||
setup: (self) => {
|
||||
let icon = NotificationIcon(n);
|
||||
if (icon !== null) {
|
||||
self.child = icon;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const title = Widget.Label({
|
||||
class_name: "title",
|
||||
xalign: 0,
|
||||
justification: "left",
|
||||
hexpand: true,
|
||||
max_width_chars: 24,
|
||||
truncate: "end",
|
||||
wrap: true,
|
||||
label: n.summary,
|
||||
use_markup: true,
|
||||
});
|
||||
|
||||
const body = Widget.Label({
|
||||
class_name: "body",
|
||||
hexpand: true,
|
||||
use_markup: true,
|
||||
xalign: 0,
|
||||
justification: "left",
|
||||
max_width_chars: 100,
|
||||
wrap: true,
|
||||
label: n.body,
|
||||
});
|
||||
|
||||
const actions = Widget.Box({
|
||||
class_name: "actions",
|
||||
children: n.actions
|
||||
.filter(({ id }) => id != "default")
|
||||
.map(({ id, label }) =>
|
||||
Widget.Button({
|
||||
class_name: "action-button",
|
||||
on_clicked: () => n.invoke(id),
|
||||
hexpand: true,
|
||||
child: Widget.Label(label),
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
return Widget.EventBox({
|
||||
on_primary_click: () => {
|
||||
if (n.actions.length > 0) n.invoke(n.actions[0].id);
|
||||
},
|
||||
on_middle_click: closeAll,
|
||||
on_secondary_click: () => n.dismiss(),
|
||||
child: Widget.Box({
|
||||
class_name: `notification ${n.urgency}`,
|
||||
vertical: true,
|
||||
|
||||
children: [
|
||||
Widget.Box({
|
||||
class_name: "info",
|
||||
children: [
|
||||
icon,
|
||||
Widget.Box({
|
||||
vertical: true,
|
||||
class_name: "text",
|
||||
vpack: "center",
|
||||
|
||||
setup: (self) => {
|
||||
if (n.body.length > 0) {
|
||||
self.children = [title, body];
|
||||
} else {
|
||||
self.children = [title];
|
||||
}
|
||||
},
|
||||
}),
|
||||
],
|
||||
}),
|
||||
actions,
|
||||
],
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
let lastMonitor;
|
||||
export const notificationPopup = () =>
|
||||
Widget.Window({
|
||||
name: "notifications",
|
||||
anchor: ["top", "right"],
|
||||
child: Widget.Box({
|
||||
css: "padding: 1px;",
|
||||
class_name: "notifications",
|
||||
vertical: true,
|
||||
// @ts-ignore
|
||||
children: Notifications.bind("popups").transform((popups) => {
|
||||
return popups.map(Notification);
|
||||
}),
|
||||
}),
|
||||
}).hook(Hyprland.active, (self) => {
|
||||
// prevent useless resets
|
||||
if (lastMonitor === Hyprland.active.monitor) return;
|
||||
|
||||
self.monitor = Hyprland.active.monitor.id;
|
||||
});
|
||||
|
||||
export default notificationPopup;
|
||||
86
home/core/gui/ags/windows/osd/main.js
Normal file
86
home/core/gui/ags/windows/osd/main.js
Normal file
@@ -0,0 +1,86 @@
|
||||
import App from "resource:///com/github/Aylur/ags/app.js";
|
||||
import { Audio, Hyprland, Widget } from "../../imports.js";
|
||||
|
||||
import Brightness from "../../services/brightness.js";
|
||||
import Indicators from "../../services/osd.js";
|
||||
import PopupWindow from "../../utils/popup_window.js";
|
||||
|
||||
// connections
|
||||
Audio.connect("speaker-changed", () =>
|
||||
Audio.speaker.connect("changed", () => {
|
||||
if (!App.getWindow("system-menu")?.visible) {
|
||||
Indicators.speaker();
|
||||
}
|
||||
}),
|
||||
);
|
||||
Audio.connect("microphone-changed", () =>
|
||||
Audio.microphone.connect("changed", () => Indicators.mic()),
|
||||
);
|
||||
|
||||
Brightness.connect("screen-changed", () => {
|
||||
if (!App.getWindow("system-menu")?.visible) {
|
||||
Indicators.display();
|
||||
}
|
||||
});
|
||||
|
||||
let lastMonitor;
|
||||
|
||||
const child = () =>
|
||||
Widget.Box({
|
||||
className: "osd",
|
||||
|
||||
children: [
|
||||
Widget.Overlay({
|
||||
hexpand: true,
|
||||
visible: false,
|
||||
passThrough: true,
|
||||
|
||||
child: Widget.ProgressBar({
|
||||
hexpand: true,
|
||||
vertical: false,
|
||||
}).hook(Indicators, (self, props) => {
|
||||
self.value = props?.value ?? 0;
|
||||
self.visible = props?.showProgress ?? false;
|
||||
}),
|
||||
|
||||
overlays: [
|
||||
Widget.Box({
|
||||
hexpand: true,
|
||||
|
||||
children: [
|
||||
Widget.Icon().hook(
|
||||
Indicators,
|
||||
(self, props) => (self.icon = props?.icon ?? ""),
|
||||
),
|
||||
Widget.Box({
|
||||
hexpand: true,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
export default () =>
|
||||
PopupWindow({
|
||||
name: "osd",
|
||||
monitor: 0,
|
||||
layer: "overlay",
|
||||
child: child(),
|
||||
click_through: true,
|
||||
anchor: ["bottom"],
|
||||
revealerSetup: (self) =>
|
||||
self.hook(Indicators, (revealer, _, visible) => {
|
||||
revealer.reveal_child = visible;
|
||||
}),
|
||||
})
|
||||
.hook(Hyprland.active, (self) => {
|
||||
// prevent useless resets
|
||||
if (lastMonitor === Hyprland.active.monitor) return;
|
||||
|
||||
self.monitor = Hyprland.active.monitor.id;
|
||||
})
|
||||
.hook(Indicators, (win, _, visible) => {
|
||||
win.visible = visible;
|
||||
});
|
||||
52
home/core/gui/ags/windows/system-menu/battery_info.js
Normal file
52
home/core/gui/ags/windows/system-menu/battery_info.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import { App, Battery, Icons, Utils, Widget } from "../../imports.js";
|
||||
import { batteryTime } from "../../utils/battery.js";
|
||||
|
||||
const batteryEnergy = () => {
|
||||
return Battery.energyRate > 0.1 ? `${Battery.energyRate.toFixed(1)} W ` : "";
|
||||
};
|
||||
|
||||
const BatteryIcon = () =>
|
||||
Widget.Icon()
|
||||
.bind("icon", Battery, "percent", () => Battery.iconName)
|
||||
.bind("tooltip-text", Battery, "energy-rate", batteryEnergy);
|
||||
|
||||
const BatteryPercent = () =>
|
||||
Widget.Label().bind("label", Battery, "percent", (percent) => `${percent}%`);
|
||||
|
||||
const BatteryTime = () =>
|
||||
Widget.Label({
|
||||
className: "time",
|
||||
vexpand: true,
|
||||
vpack: "center",
|
||||
})
|
||||
.bind("label", Battery, "charging", batteryTime)
|
||||
.bind("label", Battery, "energy-rate", batteryTime);
|
||||
|
||||
const BatteryBox = () =>
|
||||
Widget.Box({
|
||||
className: "battery-box",
|
||||
visible: Battery.available,
|
||||
|
||||
children: [BatteryIcon(), BatteryPercent(), BatteryTime()],
|
||||
});
|
||||
|
||||
const PowerButton = () =>
|
||||
Widget.Button({
|
||||
className: "button disabled",
|
||||
hexpand: true,
|
||||
hpack: "end",
|
||||
|
||||
onPrimaryClick: () => {
|
||||
App.toggleWindow("system-menu");
|
||||
Utils.exec("wlogout");
|
||||
},
|
||||
|
||||
child: Widget.Icon(Icons.powerButton),
|
||||
});
|
||||
|
||||
export default () =>
|
||||
Widget.Box({
|
||||
className: "battery-info",
|
||||
|
||||
children: [BatteryBox(), PowerButton()],
|
||||
});
|
||||
22
home/core/gui/ags/windows/system-menu/main.js
Normal file
22
home/core/gui/ags/windows/system-menu/main.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Widget } from "../../imports.js";
|
||||
import PopupWindow from "../../utils/popup_window.js";
|
||||
|
||||
import Toggles from "./toggles.js";
|
||||
import Sliders from "./sliders.js";
|
||||
import BatteryInfo from "./battery_info.js";
|
||||
|
||||
const SystemMenuBox = () =>
|
||||
Widget.Box({
|
||||
className: "system-menu",
|
||||
vertical: true,
|
||||
|
||||
children: [Toggles(), Sliders(), BatteryInfo()],
|
||||
});
|
||||
|
||||
export default () =>
|
||||
PopupWindow({
|
||||
monitor: 0,
|
||||
anchor: ["top", "right"],
|
||||
name: "system-menu",
|
||||
child: SystemMenuBox(),
|
||||
});
|
||||
74
home/core/gui/ags/windows/system-menu/sliders.js
Normal file
74
home/core/gui/ags/windows/system-menu/sliders.js
Normal file
@@ -0,0 +1,74 @@
|
||||
import { App, Audio, Icons, Utils, Widget } from "../../imports.js";
|
||||
import Brightness from "../../services/brightness.js";
|
||||
import { audioIcon } from "../../utils/audio.js";
|
||||
|
||||
const Slider = (args) =>
|
||||
Widget.Box({
|
||||
...(args.props ?? {}),
|
||||
className: args.name,
|
||||
|
||||
children: [
|
||||
Widget.Button({
|
||||
onPrimaryClick: args.icon.action ?? null,
|
||||
child: Widget.Icon({
|
||||
icon: args.icon.icon ?? "",
|
||||
setup: args.icon.setup,
|
||||
}),
|
||||
}),
|
||||
Widget.Slider({
|
||||
drawValue: false,
|
||||
hexpand: true,
|
||||
setup: args.slider.setup,
|
||||
onChange: args.slider.onChange ?? null,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const vol = () => {
|
||||
return {
|
||||
name: "volume",
|
||||
icon: {
|
||||
icon: "",
|
||||
action: () => {
|
||||
App.toggleWindow("system-menu");
|
||||
Utils.execAsync("pwvucontrol");
|
||||
},
|
||||
setup: (self) =>
|
||||
self
|
||||
.bind("icon", Audio.speaker, "volume", audioIcon)
|
||||
.bind("icon", Audio.speaker.stream, "is-muted", audioIcon),
|
||||
},
|
||||
slider: {
|
||||
setup: (self) => self.bind("value", Audio.speaker, "volume"),
|
||||
onChange: ({ value }) => (Audio.speaker.volume = value),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const brightness = () => {
|
||||
return {
|
||||
name: "brightness",
|
||||
icon: {
|
||||
icon: Icons.brightness,
|
||||
},
|
||||
slider: {
|
||||
setup: (self) => self.bind("value", Brightness, "screen-value"),
|
||||
onChange: ({ value }) => (Brightness.screenValue = value),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default () =>
|
||||
Widget.Box({
|
||||
className: "sliders",
|
||||
vertical: true,
|
||||
|
||||
// The Audio service is ready later than ags is done parsing the config,
|
||||
// so only build the widget when we receive a signal from it.
|
||||
setup: (self) => {
|
||||
const connID = Audio.connect("notify::speaker", () => {
|
||||
Audio.disconnect(connID);
|
||||
self.children = [Slider(vol()), Slider(brightness())];
|
||||
});
|
||||
},
|
||||
});
|
||||
102
home/core/gui/ags/windows/system-menu/toggles.js
Normal file
102
home/core/gui/ags/windows/system-menu/toggles.js
Normal file
@@ -0,0 +1,102 @@
|
||||
import { App, Bluetooth, Network, Utils, Widget } from "../../imports.js";
|
||||
|
||||
import { getNetIcon, getNetText } from "../../utils/net.js";
|
||||
import { getBluetoothIcon, getBluetoothText } from "../../utils/bluetooth.js";
|
||||
|
||||
const Toggle = (args) =>
|
||||
Widget.Box({
|
||||
...(args.props ?? {}),
|
||||
className: `toggle ${args.name}`,
|
||||
hexpand: true,
|
||||
hpack: "start",
|
||||
|
||||
children: [
|
||||
Widget.Button({
|
||||
className: "button",
|
||||
|
||||
child: Widget.Icon({
|
||||
setup: args.icon.setup,
|
||||
}),
|
||||
setup: args.icon.buttonSetup,
|
||||
}),
|
||||
Widget.Button({
|
||||
hexpand: true,
|
||||
child: Widget.Label({
|
||||
hpack: "start",
|
||||
setup: args.label.setup,
|
||||
}),
|
||||
setup: args.label.buttonSetup,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const net = {
|
||||
name: "net",
|
||||
icon: {
|
||||
setup: (self) =>
|
||||
self
|
||||
.bind("icon", Network, "connectivity", getNetIcon)
|
||||
.bind("icon", Network.wifi, "icon-name", getNetIcon),
|
||||
|
||||
buttonSetup: (self) => {
|
||||
self.onPrimaryClick = () => Network.toggleWifi();
|
||||
self.hook(
|
||||
Network,
|
||||
(btn) =>
|
||||
btn.toggleClassName("disabled", Network.connectivity != "full"),
|
||||
"notify::connectivity",
|
||||
);
|
||||
},
|
||||
},
|
||||
label: {
|
||||
setup: (self) =>
|
||||
self
|
||||
.bind("label", Network, "connectivity", () => getNetText())
|
||||
.bind("label", Network.wifi, "ssid", () => getNetText()),
|
||||
|
||||
buttonSetup: (self) => {
|
||||
self.onPrimaryClick = () => {
|
||||
App.toggleWindow("system-menu");
|
||||
Utils.execAsync([
|
||||
"sh",
|
||||
"-c",
|
||||
"XDG_CURRENT_DESKTOP=GNOME gnome-control-center",
|
||||
]);
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const bt = {
|
||||
name: "bluetooth",
|
||||
icon: {
|
||||
setup: (self) =>
|
||||
self.bind("icon", Bluetooth, "connected-devices", getBluetoothIcon),
|
||||
buttonSetup: (self) => {
|
||||
self.onPrimaryClick = () => Bluetooth.toggle();
|
||||
self.hook(
|
||||
Bluetooth,
|
||||
(btn) => btn.toggleClassName("disabled", !Bluetooth.enabled),
|
||||
"notify::enabled",
|
||||
);
|
||||
},
|
||||
},
|
||||
label: {
|
||||
setup: (self) =>
|
||||
self.bind("label", Bluetooth, "connected-devices", getBluetoothText),
|
||||
buttonSetup: (self) => {
|
||||
self.onPrimaryClick = () => {
|
||||
App.toggleWindow("system-menu");
|
||||
Utils.execAsync("overskride");
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default () =>
|
||||
Widget.Box({
|
||||
className: "toggles",
|
||||
vertical: true,
|
||||
|
||||
children: [Toggle(net), Toggle(bt)],
|
||||
});
|
||||
@@ -18,6 +18,8 @@
|
||||
})
|
||||
{
|
||||
# "<C-c>" = "<cmd> %y+ <CR>";
|
||||
"<C-v>" = "p";
|
||||
"<C-a>" = "<cmd>%y<CR>";
|
||||
"<Space>" = "<NOP>";
|
||||
|
||||
# Esc to clear search results
|
||||
@@ -64,6 +66,7 @@
|
||||
inherit action key;
|
||||
})
|
||||
{
|
||||
"<C-c>" = "y";
|
||||
# better indenting
|
||||
">" = ">gv";
|
||||
"<" = "<gv";
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
number = true; # Display the absolute line number of the current line
|
||||
hidden = true; # Keep closed buffer open in the background
|
||||
mouse = "a"; # Enable mouse control
|
||||
mousemodel = "extend"; # Mouse right-click extends the current selection
|
||||
mousemodel = "popup"; # Mouse right-click extends the current selection
|
||||
splitbelow = true; # A new window is put below the current one
|
||||
splitright = true; # A new window is put right of the current one
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
imports = [
|
||||
# core.gui
|
||||
../../core/gui/gtk
|
||||
../../core/gui/mako
|
||||
# ../../core/gui/mako
|
||||
../../core/gui/waybar
|
||||
../../core/gui/ags
|
||||
../../core/gui/xdg
|
||||
|
||||
@@ -46,7 +46,6 @@
|
||||
curl
|
||||
fzf
|
||||
gnome-disk-utility
|
||||
killall
|
||||
lazygit
|
||||
ntfs3g
|
||||
p7zip
|
||||
@@ -100,6 +99,19 @@
|
||||
prettierd
|
||||
black
|
||||
vimPlugins.nvim-treesitter-parsers.typescript
|
||||
|
||||
# ags_dependencies
|
||||
bash
|
||||
coreutils
|
||||
dart-sass
|
||||
gawk
|
||||
imagemagick
|
||||
procps
|
||||
util-linux
|
||||
gnome.gnome-control-center
|
||||
mission-center
|
||||
overskride
|
||||
wlogout
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user