update system

This commit is contained in:
2024-04-17 10:03:57 +02:00
parent 6e0e34e425
commit 4c78f4b5e6
260 changed files with 8100 additions and 7799 deletions

37
flake.lock generated
View File

@@ -335,6 +335,24 @@
"type": "github"
}
},
"matugen": {
"inputs": {
"nixpkgs": "nixpkgs_3"
},
"locked": {
"lastModified": 1711657889,
"narHash": "sha256-4VX7Rt+ftEH8nwg59eT7TsvHYUf8/euUmwh/JLc4rLc=",
"owner": "InioX",
"repo": "matugen",
"rev": "566277529dadc2b149a8bd8b9859ea791ecdef26",
"type": "github"
},
"original": {
"owner": "InioX",
"repo": "matugen",
"type": "github"
}
},
"nix-darwin": {
"inputs": {
"nixpkgs": [
@@ -389,6 +407,22 @@
}
},
"nixpkgs_3": {
"locked": {
"lastModified": 1691186842,
"narHash": "sha256-wxBVCvZUwq+XS4N4t9NqsHV4E64cPVqQ2fdDISpjcw0=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "18036c0be90f4e308ae3ebcab0e14aae0336fe42",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_4": {
"locked": {
"lastModified": 1712791164,
"narHash": "sha256-3sbWO1mbpWsLepZGbWaMovSO7ndZeFqDSdX0hZ9nVyw=",
@@ -479,7 +513,8 @@
"anyrun": "anyrun",
"home-manager": "home-manager",
"hyprland": "hyprland",
"nixpkgs": "nixpkgs_3",
"matugen": "matugen",
"nixpkgs": "nixpkgs_4",
"nixvim": "nixvim",
"nur": "nur"
}

View File

@@ -6,6 +6,7 @@
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
matugen.url = "github:InioX/matugen";
ags.url = "github:Aylur/ags";
nixvim = {
url = "github:nix-community/nixvim";
@@ -27,6 +28,7 @@
nixvim,
anyrun,
hyprland,
matugen,
...
}@inputs:
let
@@ -50,10 +52,12 @@
hyprland = hyprland.packages.${prev.system}.hyprland;
xdg-desktop-portal-hyprland = hyprland.packages.${prev.system}.xdg-desktop-portal-hyprland;
ags = ags.packages.${prev.system}.default;
matugen = matugen.packages.${prev.system}.default;
gbmonctl = prev.callPackage ./overlays/gbmonctl { };
lpc21isp = prev.callPackage ./overlays/lpc21isp { };
darkman = prev.callPackage ./overlays/darkman { };
cura = prev.callPackage ./overlays/cura { };
asztal = prev.callPackage ./overlays/asztal { };
})
];
}

View File

@@ -14,6 +14,7 @@
inputs.ags.homeManagerModules.default
inputs.nixvim.homeManagerModules.nixvim
inputs.anyrun.homeManagerModules.default
./shell/asztal.nix
./clean-home-dir.nix
./programs/neovide.nix
# ./default-apps.nix

View File

@@ -1,77 +0,0 @@
env:
es2021: true
extends: eslint:recommended
overrides: []
parserOptions:
ecmaVersion: latest
sourceType: "module"
rules:
arrow-parens:
- error
- as-needed
comma-dangle:
- error
- always-multiline
comma-spacing:
- error
- before: false
after: true
comma-style:
- error
- last
curly:
- error
- multi-or-nest
- consistent
dot-location:
- error
- property
eol-last: error
indent:
- error
- 4
- SwitchCase: 1
keyword-spacing:
- error
- before: true
lines-between-class-members:
- error
- always
- exceptAfterSingleLine: true
padded-blocks:
- error
- never
- allowSingleLineBlocks: false
prefer-const: error
quotes:
- error
- single
- avoidEscape: true
semi:
- error
- always
nonblock-statement-body-position:
- error
- below
no-trailing-spaces:
- error
array-bracket-spacing:
- error
- never
key-spacing:
- error
- beforeColon: false
afterColon: true
object-curly-spacing:
- error
- always
no-useless-escape:
- off
globals:
pkg: readonly
ags: readonly
ARGV: readonly
imports: readonly
print: readonly
console: readonly
logError: readonly

View File

@@ -1,14 +0,0 @@
extends: stylelint-config-standard-scss
ignoreFiles:
- "**/*.js"
- "**/*.ts"
rules:
selector-type-no-unknown: null
declaration-empty-line-before: null
no-descending-specificity: null
selector-pseudo-class-no-unknown: null
color-function-notation: legacy
alpha-value-notation: number
scss/operator-no-unspaced: null
scss/no-global-function-names: null
scss/dollar-variable-empty-line-before: null

Binary file not shown.

Before

Width:  |  Height:  |  Size: 428 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 MiB

View File

@@ -1,2 +0,0 @@
import { default as main } from "./js/main.js";
export default main;

View File

@@ -1,96 +0,0 @@
import PopupWindow from "../misc/PopupWindow.js";
import icons from "../icons.js";
const pkg = JSON.parse(Utils.readFile(App.configDir + "/package.json"));
const show = JSON.parse(
Utils.readFile(Utils.CACHE_DIR + "/show_about") || "true",
);
const dontShow = () =>
Utils.writeFile("false", Utils.CACHE_DIR + "/show_about");
const avatar = App.configDir + "/assets/aylur.jpg";
/**
* @param {Object} o
* @param {string} o.label
* @param {string} o.link
*/
const LinkButton = ({ label, link }) =>
Widget.Button({
on_clicked: () => Utils.execAsync(["xdg-open", link]),
child: Widget.Box({
children: [
Widget.Label({ label, hexpand: true, xalign: 0 }),
Widget.Icon(icons.ui.link),
],
}),
});
export default () =>
PopupWindow({
name: "about",
transition: "slide_down",
child: Widget.Box({
vertical: true,
class_name: "window-content",
children: [
Widget.Box({
class_name: "avatar",
hpack: "center",
css: `background-image: url('${avatar}');`,
}),
Widget.Box({
vertical: true,
class_name: "labels vertical",
children: [
Widget.Label({
class_name: "title",
label: pkg.description,
}),
Widget.Label({
class_name: "author",
label: pkg.author,
}),
Widget.Label({
class_name: "version",
hpack: "center",
label: pkg.version,
}),
],
}),
Widget.Box({
class_name: "buttons",
vertical: true,
vexpand: true,
vpack: "end",
children: [
LinkButton({
label: "Support me on Ko-fi",
link: pkg.kofi,
}),
LinkButton({
label: "Report an Issue",
link: pkg.bugs.url,
}),
],
}),
Widget.Button({
class_name: "dont-show",
on_clicked: () => {
dontShow();
App.toggleWindow("about");
},
child: Widget.Box({
children: [
Widget.Label("Don't show again"),
Widget.Box({ hexpand: true }),
Widget.Icon(icons.ui.close),
],
}),
}),
],
}),
});
export function showAbout(force = false) {
if (show || force) App.toggleWindow("about");
}

View File

@@ -1,44 +0,0 @@
import options from "../options.js";
/** @param {import('resource:///com/github/Aylur/ags/service/applications.js').Application} app */
export default (app) => {
const title = Widget.Label({
class_name: "title",
label: app.name,
xalign: 0,
vpack: "center",
truncate: "end",
});
const description = Widget.Label({
class_name: "description",
label: app.description || "",
wrap: true,
xalign: 0,
justification: "left",
vpack: "center",
});
const icon = Widget.Icon({
icon: Utils.lookUpIcon(app.icon_name || "") ? app.icon_name || "" : "",
size: options.applauncher.icon_size.bind("value"),
});
const textBox = Widget.Box({
vertical: true,
vpack: "center",
children: app.description ? [title, description] : [title],
});
return Widget.Button({
class_name: "app-item",
attribute: app,
child: Widget.Box({
children: [icon, textBox],
}),
on_clicked: () => {
App.closeWindow("applauncher");
app.launch();
},
});
};

View File

@@ -1,90 +0,0 @@
import Applications from "resource:///com/github/Aylur/ags/service/applications.js";
import PopupWindow from "../misc/PopupWindow.js";
import AppItem from "./AppItem.js";
import icons from "../icons.js";
import { launchApp } from "../utils.js";
import options from "../options.js";
const WINDOW_NAME = "applauncher";
const Applauncher = () => {
const mkItems = () => [
Widget.Separator({ hexpand: true }),
...Applications.query("").flatMap((app) =>
Widget.Revealer({
setup: (w) => (w.attribute = { app, revealer: w }),
child: Widget.Box({
vertical: true,
children: [
Widget.Separator({ hexpand: true }),
AppItem(app),
Widget.Separator({ hexpand: true }),
],
}),
}),
),
Widget.Separator({ hexpand: true }),
];
let items = mkItems();
const list = Widget.Box({
class_name: "app-list",
vertical: true,
children: items,
});
const entry = Widget.Entry({
hexpand: true,
primary_icon_name: icons.apps.search,
// set some text so on-change works the first time
text: "-",
on_accept: ({ text }) => {
const list = Applications.query(text || "");
if (list[0]) {
App.toggleWindow(WINDOW_NAME);
launchApp(list[0]);
}
},
on_change: ({ text }) =>
items.map((item) => {
if (item.attribute) {
const { app, revealer } = item.attribute;
revealer.reveal_child = app.match(text);
}
}),
});
return Widget.Box({
vertical: true,
children: [
entry,
Widget.Scrollable({
hscroll: "never",
child: list,
}),
],
setup: (self) =>
self.hook(App, (_, win, visible) => {
if (win !== WINDOW_NAME) return;
entry.text = "-";
entry.text = "";
if (visible) {
entry.grab_focus();
} else {
items = mkItems();
list.children = items;
}
}),
});
};
export default () =>
PopupWindow({
name: WINDOW_NAME,
transition: "slide_down",
child: Applauncher(),
anchor: options.applauncher.anchor.bind("value"),
});

View File

@@ -1,34 +0,0 @@
/**
* @typedef {Object} PanelButtonProps
* @property {import('types/widgets/button').ButtonProps['child']} content
* @property {string=} window
*/
/**
* @param {import('types/widgets/button').ButtonProps & PanelButtonProps} o
*/
export default ({ class_name, content, window = "", setup, ...rest }) =>
Widget.Button({
class_name: `panel-button ${class_name}`,
child: Widget.Box({ children: [content] }),
setup: (self) => {
let open = false;
self.hook(App, (_, win, visible) => {
if (win !== window) return;
if (open && !visible) {
open = false;
self.toggleClassName("active", false);
}
if (visible) {
open = true;
self.toggleClassName("active");
}
});
if (setup) setup(self);
},
...rest,
});

View File

@@ -1,113 +0,0 @@
import SystemTray from "resource:///com/github/Aylur/ags/service/systemtray.js";
import Notifications from "resource:///com/github/Aylur/ags/service/notifications.js";
import Mpris from "resource:///com/github/Aylur/ags/service/mpris.js";
import Battery from "resource:///com/github/Aylur/ags/service/battery.js";
import OverviewButton from "./buttons/OverviewButton.js";
import Workspaces from "./buttons/Workspaces.js";
import FocusedClient from "./buttons/FocusedClient.js";
import MediaIndicator from "./buttons/MediaIndicator.js";
import DateButton from "./buttons/DateButton.js";
import NotificationIndicator from "./buttons/NotificationIndicator.js";
import SysTray from "./buttons/SysTray.js";
import ColorPicker from "./buttons/ColorPicker.js";
import SystemIndicators from "./buttons/SystemIndicators.js";
import PowerMenu from "./buttons/PowerMenu.js";
import ScreenRecord from "./buttons/ScreenRecord.js";
import BatteryBar from "./buttons/BatteryBar.js";
import SubMenu from "./buttons/SubMenu.js";
import Recorder from "../services/screenrecord.js";
// import * as System from './buttons/System.js';
// import Taskbar from './buttons/Taskbar.js';
import options from "../options.js";
const submenuItems = Variable(1);
SystemTray.connect("changed", () => {
submenuItems.setValue(SystemTray.items.length + 1);
});
/**
* @template {import('types/service').default} T
* @param {T=} service
* @param {(service: T) => boolean=} condition
*/
const SeparatorDot = (service, condition) =>
Widget.Separator({
vpack: "center",
setup: (self) => {
const visibility = () => {
if (!options.bar.separators.value) return (self.visible = false);
self.visible =
condition && service
? condition(service)
: options.bar.separators.value;
};
if (service && condition) self.hook(service, visibility);
self.on("draw", visibility);
self.bind("visible", options.bar.separators);
},
});
const Start = () =>
Widget.Box({
class_name: "start",
children: [
OverviewButton(),
SeparatorDot(),
Workspaces(),
SeparatorDot(),
FocusedClient(),
Widget.Box({ hexpand: true }),
NotificationIndicator(),
SeparatorDot(Notifications, (n) => n.notifications.length > 0 || n.dnd),
],
});
const Center = () =>
Widget.Box({
class_name: "center",
children: [DateButton()],
});
const End = () =>
Widget.Box({
class_name: "end",
children: [
SeparatorDot(Mpris, (m) => m.players.length > 0),
MediaIndicator(),
Widget.Box({ hexpand: true }),
SubMenu({
items: submenuItems,
children: [SysTray(), ColorPicker()],
}),
SeparatorDot(),
ScreenRecord(),
SeparatorDot(Recorder, (r) => r.recording),
SystemIndicators(),
SeparatorDot(Battery, (b) => b.available),
SeparatorDot(),
PowerMenu(),
],
});
/** @param {number} monitor */
export default (monitor) =>
Widget.Window({
name: `bar${monitor}`,
class_name: "transparent",
exclusivity: "exclusive",
monitor,
anchor: options.bar.position
.bind("value")
.transform((pos) => [pos, "left", "right"]),
child: Widget.CenterBox({
class_name: "panel",
start_widget: Start(),
center_widget: Center(),
end_widget: End(),
}),
});

View File

@@ -1,89 +0,0 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import Battery from "resource:///com/github/Aylur/ags/service/battery.js";
import icons from "../../icons.js";
import FontIcon from "../../misc/FontIcon.js";
import options from "../../options.js";
import PanelButton from "../PanelButton.js";
const Indicator = () =>
Widget.Stack({
children: {
false: Widget.Icon({ icon: Battery.bind("icon_name") }),
true: FontIcon(icons.battery.charging),
},
visible: options.battery.bar.show_icon.bind("value"),
setup: (self) =>
self.hook(Battery, () => {
self.shown = `${Battery.charging || Battery.charged}`;
}),
});
const PercentLabel = () =>
Widget.Revealer({
transition: "slide_right",
reveal_child: options.battery.show_percentage.bind("value"),
child: Widget.Label({
label: Battery.bind("percent").transform((p) => `${p}%`),
}),
});
const LevelBar = () =>
Widget.LevelBar({
value: Battery.bind("percent").transform((p) => p / 100),
setup: (self) =>
self.hook(options.battery.bar.full, () => {
const full = options.battery.bar.full.value;
self.vpack = full ? "fill" : "center";
self.hpack = full ? "fill" : "center";
}),
});
const WholeButton = () =>
Widget.Overlay({
class_name: "whole-button",
child: LevelBar(),
pass_through: true,
overlays: [
Widget.Box({
hpack: "center",
children: [
FontIcon({
icon: icons.battery.charging,
visible: Battery.bind("charging"),
}),
Widget.Box({
hpack: "center",
vpack: "center",
child: PercentLabel(),
}),
],
}),
],
});
export default () =>
PanelButton({
class_name: "battery-bar",
on_clicked: () => {
const v = options.battery.show_percentage.value;
options.battery.show_percentage.value = !v;
},
content: Widget.Box({
visible: Battery.bind("available"),
children: options.battery.bar.full
.bind("value")
.transform((full) =>
full ? [WholeButton()] : [Indicator(), PercentLabel(), LevelBar()],
),
setup: (self) =>
self.hook(Battery, (w) => {
w.toggleClassName("charging", Battery.charging || Battery.charged);
w.toggleClassName(
"medium",
Battery.percent < options.battery.medium.value,
);
w.toggleClassName("low", Battery.percent < options.battery.low.value);
w.toggleClassName("half", Battery.percent < 48);
}),
}),
});

View File

@@ -1,26 +0,0 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import Colors from "../../services/colorpicker.js";
import PanelButton from "../PanelButton.js";
import Gdk from "gi://Gdk";
export default () =>
PanelButton({
class_name: "color-picker",
content: Widget.Icon("color-select-symbolic"),
tooltip_text: Colors.bind("colors").transform((v) => `${v.length} colors`),
on_clicked: () => Colors.pick(),
on_secondary_click: (btn) => {
if (Colors.colors.length === 0) return;
Widget.Menu({
class_name: "colorpicker",
children: Colors.colors.map((color) =>
Widget.MenuItem({
child: Widget.Label(color),
css: `background-color: ${color}`,
on_activate: () => Colors.wlCopy(color),
}),
),
}).popup_at_widget(btn, Gdk.Gravity.SOUTH, Gdk.Gravity.NORTH, null);
},
});

View File

@@ -1,11 +0,0 @@
import App from "resource:///com/github/Aylur/ags/app.js";
import Clock from "../../misc/Clock.js";
import PanelButton from "../PanelButton.js";
export default ({ format = "%R - %x" } = {}) =>
PanelButton({
class_name: "dashboard panel-button",
on_clicked: () => App.toggleWindow("dashboard"),
window: "dashboard",
content: Clock({ format }),
});

View File

@@ -1,44 +0,0 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import Hyprland from "resource:///com/github/Aylur/ags/service/hyprland.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import PanelButton from "../PanelButton.js";
import options from "../../options.js";
import { substitute } from "../../utils.js";
export const ClientLabel = () =>
Widget.Label({
label: Hyprland.active.client.bind("class").transform((c) => {
const { titles } = options.substitutions;
return substitute(titles, c);
}),
});
export const ClientIcon = () =>
Widget.Icon({
setup: (self) =>
self.hook(Hyprland.active.client, () => {
const { icons } = options.substitutions;
const { client } = Hyprland.active;
const classIcon = substitute(icons, client.class) + "-symbolic";
const titleIcon = substitute(icons, client.class) + "-symbolic";
const hasTitleIcon = Utils.lookUpIcon(titleIcon);
const hasClassIcon = Utils.lookUpIcon(classIcon);
if (hasClassIcon) self.icon = classIcon;
if (hasTitleIcon) self.icon = titleIcon;
self.visible = !!(hasTitleIcon || hasClassIcon);
}),
});
export default () =>
PanelButton({
class_name: "focused-client",
content: Widget.Box({
tooltip_text: Hyprland.active.bind("client").transform((c) => c.title),
children: [ClientIcon(), ClientLabel()],
}),
});

View File

@@ -1,74 +0,0 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import Mpris from "resource:///com/github/Aylur/ags/service/mpris.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import HoverRevealer from "../../misc/HoverRevealer.js";
import * as mpris from "../../misc/mpris.js";
import options from "../../options.js";
export const getPlayer = (name = options.mpris.preferred.value) =>
Mpris.getPlayer(name) || Mpris.players[0] || null;
/**
* @param {Object} o
* @param {import('types/service/mpris').MprisPlayer} o.player
* @param {import('../../misc/HoverRevealer').HoverRevealProps['direction']=} o.direction
*/
const Indicator = ({ player, direction = "right" }) =>
HoverRevealer({
class_name: `media panel-button ${player.name}`,
direction,
on_primary_click: () => player.playPause(),
on_scroll_up: () => player.next(),
on_scroll_down: () => player.previous(),
on_secondary_click: () => player.playPause(),
indicator: mpris.PlayerIcon(player),
child: Widget.Label({
vexpand: true,
truncate: "end",
max_width_chars: 40,
label: player
.bind("track_title")
.transform(
() => `${player.track_artists.join(", ")} - ${player.track_title}`,
),
}),
setupRevealer: (self) => {
let current = "";
self.hook(player, () => {
if (current === player.track_title) return;
current = player.track_title;
self.reveal_child = true;
Utils.timeout(3000, () => {
self.reveal_child = false;
});
});
},
});
/**
* @param {Object} o
* @param {import('../../misc/HoverRevealer').HoverRevealProps['direction']=} o.direction
*/
export default ({ direction = "right" } = {}) => {
let current = null;
const update = (box) => {
const player = getPlayer();
box.visible = !!player;
if (!player) {
current = null;
return;
}
if (current === player) return;
current = player;
box.children = [Indicator({ player, direction })];
};
return Widget.Box()
.hook(options.mpris.preferred, update)
.hook(Mpris, update, "notify::players");
};

View File

@@ -1,50 +0,0 @@
import App from "resource:///com/github/Aylur/ags/app.js";
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import Notifications from "resource:///com/github/Aylur/ags/service/notifications.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import icons from "../../icons.js";
import HoverRevealer from "../../misc/HoverRevealer.js";
/**
* @param {Object} o
* @param {import('../../misc/HoverRevealer').HoverRevealProps['direction']=} o.direction
*/
export default ({ direction = "left" } = {}) =>
HoverRevealer({
class_name: "notifications panel-button",
setupEventBox: (box) =>
box
.on("button-press-event", () => App.openWindow("dashboard"))
.hook(
Notifications,
() =>
(box.visible =
Notifications.notifications.length > 0 || Notifications.dnd),
),
setupRevealer: (self) =>
self.hook(Notifications, () => {
let title = "";
const summary = Notifications.notifications[0]?.summary;
if (title === summary) return;
title = summary;
self.reveal_child = true;
Utils.timeout(3000, () => {
self.reveal_child = false;
});
}),
direction,
indicator: Widget.Icon({
icon: Notifications.bind("dnd").transform(
(dnd) => icons.notifications[dnd ? "silent" : "noisy"],
),
}),
child: Widget.Label({
truncate: "end",
max_width_chars: 40,
label: Notifications.bind("notifications").transform(
(n) => n.reverse()[0]?.summary || "",
),
}),
});

View File

@@ -1,17 +0,0 @@
import App from "resource:///com/github/Aylur/ags/app.js";
import PanelButton from "../PanelButton.js";
import FontIcon from "../../misc/FontIcon.js";
import { distroIcon } from "../../variables.js";
import options from "../../options.js";
export default () =>
PanelButton({
class_name: "overview",
window: "overview",
on_clicked: () => App.toggleWindow("overview"),
content: FontIcon({
label: options.bar.icon.bind("value").transform((v) => {
return v === "distro-icon" ? distroIcon : v;
}),
}),
});

View File

@@ -1,11 +0,0 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import App from "resource:///com/github/Aylur/ags/app.js";
import icons from "../../icons.js";
import PanelButton from "../PanelButton.js";
export default () =>
PanelButton({
class_name: "powermenu",
content: Widget.Icon(icons.powermenu.shutdown),
on_clicked: () => App.openWindow("powermenu"),
});

View File

@@ -1,23 +0,0 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import PanelButton from "../PanelButton.js";
import Recorder from "../../services/screenrecord.js";
import icons from "../../icons.js";
export default () =>
PanelButton({
class_name: "recorder",
on_clicked: () => Recorder.stop(),
visible: Recorder.bind("recording"),
content: Widget.Box({
children: [
Widget.Icon(icons.recorder.recording),
Widget.Label({
label: Recorder.bind("timer").transform((time) => {
const sec = time % 60;
const min = Math.floor(time / 60);
return `${min}:${sec < 10 ? "0" + sec : sec}`;
}),
}),
],
}),
});

View File

@@ -1,65 +0,0 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import Variable from "resource:///com/github/Aylur/ags/variable.js";
import icons from "../../icons.js";
import options from "../../options.js";
/**
* @param {import('types/widgets/revealer').default} revealer
* @param {'left' | 'right' | 'up' | 'down'} direction
* @param {import('types/variable').Variable<number>} items
*/
const Arrow = (revealer, direction, items) => {
let deg = 0;
const icon = Widget.Icon({
icon: icons.ui.arrow[direction],
});
const animate = () => {
const t = options.transition.value / 20;
const step = revealer.reveal_child ? 10 : -10;
for (let i = 0; i < 18; ++i) {
Utils.timeout(t * i, () => {
deg += step;
icon.setCss(`-gtk-icon-transform: rotate(${deg}deg);`);
});
}
};
return Widget.Button({
class_name: "panel-button sub-menu",
tooltip_text: items.bind().transform((v) => `${v} Items`),
on_clicked: () => {
animate();
revealer.reveal_child = !revealer.reveal_child;
},
child: icon,
});
};
/**
* @param {Object} o
* @param {import('types/widgets/box').default['children']} o.children
* @param {'left' | 'right' | 'up' | 'down'=} o.direction
* @param {import('types/variable').Variable} o.items
*/
export default ({ children, direction = "left", items = Variable(0) }) => {
const posStart = direction === "up" || direction === "left";
const posEnd = direction === "down" || direction === "right";
const revealer = Widget.Revealer({
transition: `slide_${direction}`,
child: Widget.Box({
children,
}),
});
return Widget.Box({
vertical: direction === "up" || direction === "down",
children: [
posStart && revealer,
Arrow(revealer, direction, items),
posEnd && revealer,
],
});
};

View File

@@ -1,44 +0,0 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import SystemTray from "resource:///com/github/Aylur/ags/service/systemtray.js";
import PanelButton from "../PanelButton.js";
import Gdk from "gi://Gdk";
/** @param {import('types/service/systemtray').TrayItem} item */
const SysTrayItem = (item) =>
PanelButton({
class_name: "tray-item",
content: Widget.Icon({ icon: item.bind("icon") }),
tooltip_markup: item.bind("tooltip_markup"),
setup: (self) => {
const id = item.menu?.connect("popped-up", (menu) => {
self.toggleClassName("active");
menu.connect("notify::visible", (menu) => {
self.toggleClassName("active", menu.visible);
});
menu.disconnect(id);
});
if (id) self.connect("destroy", () => item.menu?.disconnect(id));
},
// @ts-expect-error popup_at_widget missing from types?
on_primary_click: (btn) =>
item.menu?.popup_at_widget(
btn,
Gdk.Gravity.SOUTH,
Gdk.Gravity.NORTH,
null,
),
// @ts-expect-error popup_at_widget missing from types?
on_secondary_click: (btn) =>
item.menu?.popup_at_widget(
btn,
Gdk.Gravity.SOUTH,
Gdk.Gravity.NORTH,
null,
),
});
export default () =>
Widget.Box().bind("children", SystemTray, "items", (i) => i.map(SysTrayItem));

View File

@@ -1,50 +0,0 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import PanelButton from "../PanelButton.js";
import * as variables from "../../variables.js";
import icons from "../../icons.js";
/** @param {'cpu' | 'ram'} type */
const System = (type) => {
const icon = Widget.Icon({
class_name: "icon",
icon: icons.system[type],
});
const progress = Widget.Box({
class_name: "progress",
child: Widget.CircularProgress({
value: variables[type].bind(),
}),
});
const revealer = Widget.Revealer({
transition: "slide_right",
child: Widget.Label({
label: variables[type].bind("value").transform((v) => {
return ` ${type}: ${Math.round(v * 100)}%`;
}),
}),
});
return PanelButton({
class_name: `system ${type}`,
on_clicked: () => (revealer.reveal_child = !revealer.reveal_child),
content: Widget.EventBox({
on_hover: () => (revealer.reveal_child = true),
on_hover_lost: () => (revealer.reveal_child = false),
child: Widget.Box({
children: [
icon,
Widget.Box({
class_name: "revealer",
child: revealer,
}),
progress,
],
}),
}),
});
};
export const CPU = () => System("cpu");
export const RAM = () => System("ram");

View File

@@ -1,134 +0,0 @@
import App from "resource:///com/github/Aylur/ags/app.js";
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import Notifications from "resource:///com/github/Aylur/ags/service/notifications.js";
import Bluetooth from "resource:///com/github/Aylur/ags/service/bluetooth.js";
import Audio from "resource:///com/github/Aylur/ags/service/audio.js";
import Network from "resource:///com/github/Aylur/ags/service/network.js";
import HoverRevealer from "../../misc/HoverRevealer.js";
import PanelButton from "../PanelButton.js";
import Asusctl from "../../services/asusctl.js";
import Indicator from "../../services/onScreenIndicator.js";
import icons from "../../icons.js";
import FontIcon from "../../misc/FontIcon.js";
const ProfileIndicator = () =>
Widget.Icon()
.bind("visible", Asusctl, "profile", (p) => p !== "Balanced")
.bind("icon", Asusctl, "profile", (i) => icons.asusctl.profile[i]);
const ModeIndicator = () =>
FontIcon()
.bind("visible", Asusctl, "mode", (m) => m !== "Hybrid")
.bind("icon", Asusctl, "mode", (i) => icons.asusctl.mode[i]);
const MicrophoneIndicator = () =>
Widget.Icon().hook(
Audio,
(icon) => {
if (!Audio.microphone) return;
const { muted, low, medium, high } = icons.audio.mic;
/** @type {Array<[number, string]>} */
const cons = [
[67, high],
[34, medium],
[1, low],
[0, muted],
];
icon.icon =
cons.find(([n]) => n <= Audio.microphone.volume * 100)?.[1] || "";
icon.visible = Audio.recorders.length > 0 || Audio.microphone.is_muted;
},
"microphone-changed",
);
const DNDIndicator = () =>
Widget.Icon({
visible: Notifications.bind("dnd"),
icon: icons.notifications.silent,
});
const BluetoothDevicesIndicator = () =>
Widget.Box().hook(
Bluetooth,
(box) => {
box.children = Bluetooth.connectedDevices.map(({ iconName, name }) =>
HoverRevealer({
indicator: Widget.Icon(iconName + "-symbolic"),
child: Widget.Label(name),
}),
);
box.visible = Bluetooth.connectedDevices.length > 0;
},
"notify::connected-devices",
);
const BluetoothIndicator = () =>
Widget.Icon({
class_name: "bluetooth",
icon: icons.bluetooth.enabled,
visible: Bluetooth.bind("enabled"),
});
const NetworkIndicator = () =>
Widget.Icon().hook(Network, (self) => {
const icon = Network[Network.primary || "wifi"]?.iconName;
self.icon = icon || "";
self.visible = !!icon;
});
const AudioIndicator = () =>
Widget.Icon().hook(
Audio,
(self) => {
if (!Audio.speaker) return;
const { muted, low, medium, high, overamplified } = icons.audio.volume;
if (Audio.speaker.is_muted) return (self.icon = muted);
/** @type {Array<[number, string]>} */
const cons = [
[101, overamplified],
[67, high],
[34, medium],
[1, low],
[0, muted],
];
self.icon =
cons.find(([n]) => n <= Audio.speaker.volume * 100)?.[1] || "";
},
"speaker-changed",
);
export default () =>
PanelButton({
class_name: "quicksettings panel-button",
on_clicked: () => App.toggleWindow("quicksettings"),
setup: (self) =>
self.hook(App, (_, win, visible) => {
self.toggleClassName("active", win === "quicksettings" && visible);
}),
on_scroll_up: () => {
Audio.speaker.volume += 0.02;
Indicator.speaker();
},
on_scroll_down: () => {
Audio.speaker.volume -= 0.02;
Indicator.speaker();
},
content: Widget.Box({
children: [
Asusctl?.available && ProfileIndicator(),
Asusctl?.available && ModeIndicator(),
DNDIndicator(),
BluetoothDevicesIndicator(),
BluetoothIndicator(),
NetworkIndicator(),
AudioIndicator(),
MicrophoneIndicator(),
],
}),
});

View File

@@ -1,31 +0,0 @@
import Hyprland from "resource:///com/github/Aylur/ags/service/hyprland.js";
import Applications from "resource:///com/github/Aylur/ags/service/applications.js";
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import PanelButton from "../PanelButton.js";
import { launchApp } from "../../utils.js";
import icons from "../../icons.js";
const focus = ({ address }) =>
Hyprland.sendMessage(`dispatch focuswindow address:${address}`);
/** @param {import('types/widgets/box').default} box */
const setChildren = (box) =>
(box.children = Hyprland.clients.map((client) => {
if (Hyprland.active.workspace.id !== client.workspace.id) return;
for (const app of Applications.list) {
if (client.class && app.match(client.class)) {
return PanelButton({
content: Widget.Icon(app.icon_name || icons.fallback.executable),
tooltip_text: app.name,
on_primary_click: () => focus(client),
on_middle_click: () => launchApp(app),
});
}
}
}));
export default () =>
Widget.Box()
.hook(Hyprland, setChildren, "notify::clients")
.hook(Hyprland, setChildren, "notify::active");

View File

@@ -1,58 +0,0 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import Hyprland from "resource:///com/github/Aylur/ags/service/hyprland.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import options from "../../options.js";
import { range } from "../../utils.js";
/** @param {any} arg */
const dispatch = (arg) => Utils.execAsync(`hyprctl dispatch workspace ${arg}`);
const Workspaces = () => {
const ws = options.workspaces.value;
return Widget.Box({
children: range(ws || 20).map((i) =>
Widget.Button({
attribute: i,
on_clicked: () => dispatch(i),
child: Widget.Label({
label: `${i}`,
class_name: "indicator",
vpack: "center",
}),
setup: (self) =>
self.hook(Hyprland, () => {
self.toggleClassName("active", Hyprland.active.workspace.id === i);
self.toggleClassName(
"occupied",
(Hyprland.getWorkspace(i)?.windows || 0) > 0,
);
}),
}),
),
setup: (box) => {
if (ws === 0) {
box.hook(Hyprland.active.workspace, () =>
box.children.map((btn) => {
btn.visible = Hyprland.workspaces.some(
(ws) => ws.id === btn.attribute,
);
}),
);
}
},
});
};
export default () =>
Widget.EventBox({
class_name: "workspaces panel-button",
child: Widget.Box({
// its nested like this to keep it consistent with other PanelButton widgets
child: Widget.EventBox({
on_scroll_up: () => dispatch("m+1"),
on_scroll_down: () => dispatch("m-1"),
class_name: "eventbox",
child: options.workspaces.bind("value").transform(Workspaces),
}),
}),
});

View File

@@ -1,26 +0,0 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import DateColumn from "./DateColumn.js";
import NotificationColumn from "./NotificationColumn.js";
import PopupWindow from "../misc/PopupWindow.js";
import options from "../options.js";
export default () =>
PopupWindow({
name: "dashboard",
setup: (self) =>
self.hook(options.bar.position, () => {
self.anchor = [options.bar.position.value];
if (options.bar.position.value === "top")
self.transition = "slide_down";
if (options.bar.position.value === "bottom")
self.transition = "slide_up";
}),
child: Widget.Box({
children: [
NotificationColumn(),
Widget.Separator({ orientation: 1 }),
DateColumn(),
],
}),
});

View File

@@ -1,63 +0,0 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import icons from "../icons.js";
import Clock from "../misc/Clock.js";
import * as vars from "../variables.js";
import options from "../options.js";
/**
* @param {'cpu' | 'ram' | 'temp'} type
* @param {string} title
* @param {string} unit
*/
const SysProgress = (type, title, unit) =>
Widget.Box({
class_name: `circular-progress-box ${type}`,
hexpand: true,
tooltip_text: vars[type]
.bind("value")
.transform((v) => `${title}: ${Math.floor(v * 100)}${unit}`),
child: Widget.CircularProgress({
hexpand: true,
class_name: `circular-progress ${type}`,
child: Widget.Icon(icons.system[type]),
start_at: 0.75,
value: vars[type].bind(),
rounded: options.radii.bind("value").transform((v) => v > 0),
}),
});
export default () =>
Widget.Box({
vertical: true,
class_name: "datemenu vertical",
children: [
Widget.Box({
class_name: "clock-box",
vertical: true,
children: [
Clock({ format: "%H:%M" }),
Widget.Label({
class_name: "uptime",
label: vars.uptime.bind("value").transform((t) => `uptime: ${t}`),
}),
],
}),
Widget.Box({
class_name: "calendar",
children: [
Widget.Calendar({
hexpand: true,
hpack: "center",
}),
],
}),
Widget.Box({
class_name: "system-info horizontal",
children: [
SysProgress("cpu", "Cpu", "%"),
SysProgress("ram", "Ram", "%"),
SysProgress("temp", "Temperature", "°"),
],
}),
],
});

View File

@@ -1,81 +0,0 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import Notifications from "resource:///com/github/Aylur/ags/service/notifications.js";
import icons from "../icons.js";
import Notification from "../misc/Notification.js";
import { timeout } from "resource:///com/github/Aylur/ags/utils.js";
const ClearButton = () =>
Widget.Button({
on_clicked: () => {
const list = Array.from(Notifications.notifications);
for (let i = 0; i < list.length; i++)
timeout(50 * i, () => list[i]?.close());
},
sensitive: Notifications.bind("notifications").transform(
(n) => n.length > 0,
),
child: Widget.Box({
children: [
Widget.Label("Clear "),
Widget.Icon({
icon: Notifications.bind("notifications").transform(
(n) => icons.trash[n.length > 0 ? "full" : "empty"],
),
}),
],
}),
});
const Header = () =>
Widget.Box({
class_name: "header",
children: [
Widget.Label({ label: "Notifications", hexpand: true, xalign: 0 }),
ClearButton(),
],
});
const NotificationList = () =>
Widget.Box({
vertical: true,
vexpand: true,
children: Notifications.bind("notifications").transform((n) =>
n.reverse().map(Notification),
),
visible: Notifications.bind("notifications").transform((n) => n.length > 0),
});
const Placeholder = () =>
Widget.Box({
class_name: "placeholder",
vertical: true,
vpack: "center",
hpack: "center",
vexpand: true,
hexpand: true,
visible: Notifications.bind("notifications").transform(
(n) => n.length === 0,
),
children: [
Widget.Icon(icons.notifications.silent),
Widget.Label("Your inbox is empty"),
],
});
export default () =>
Widget.Box({
class_name: "notifications",
vertical: true,
children: [
Header(),
Widget.Scrollable({
vexpand: true,
class_name: "notification-scrollable",
child: Widget.Box({
class_name: "notification-list",
vertical: true,
children: [NotificationList(), Placeholder()],
}),
}),
],
});

View File

@@ -1,69 +0,0 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import Clock from "../misc/Clock.js";
import DesktopMenu from "./DesktopMenu.js";
import options from "../options.js";
const DesktopClock = () =>
Widget.Box({
class_name: "clock-box-shadow",
child: Widget.CenterBox({
class_name: "clock-box",
start_widget: Clock({
class_name: "clock",
hpack: "center",
format: "%H",
}),
center_widget: Widget.Box({
class_name: "separator-box",
vertical: true,
hexpand: true,
hpack: "center",
children: [
Widget.Separator({ vpack: "center", vexpand: true }),
Widget.Separator({ vpack: "center", vexpand: true }),
],
}),
end_widget: Clock({
class_name: "clock",
hpack: "center",
format: "%M",
}),
}),
});
const Desktop = () =>
Widget.EventBox({
on_secondary_click: (_, event) => DesktopMenu().popup_at_pointer(event),
child: Widget.Box({
vertical: true,
vexpand: true,
hexpand: true,
visible: options.desktop.clock.enable.bind("value"),
setup: (self) =>
self.hook(options.desktop.clock.position, () => {
const [hpack = "center", vpack = "center", offset = 64] =
options.desktop.clock.position.value.split(" ") || [];
// @ts-expect-error
self.hpack = hpack;
self.vpack = vpack;
self.setCss(`margin: ${Number(offset)}px;`);
}),
children: [
DesktopClock(),
Clock({ format: "%B %e. %A", class_name: "date" }),
],
}),
});
/** @param {number} monitor */
export default (monitor) =>
Widget.Window({
monitor,
keymode: "on-demand",
name: `desktop${monitor}`,
layer: "background",
class_name: "desktop",
anchor: ["top", "bottom", "left", "right"],
child: Desktop(),
});

View File

@@ -1,66 +0,0 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import App from "resource:///com/github/Aylur/ags/app.js";
import PowerMenu from "../services/powermenu.js";
import icons from "../icons.js";
import Gtk from "gi://Gtk";
import { openSettings } from "../settings/theme.js";
/**
* @param {string} label
* @param {string} icon
* @param {import('types/widgets/menu').MenuItemProps['on_activate']} on_activate
*/
const Item = (label, icon, on_activate) =>
Widget.MenuItem({
on_activate,
child: Widget.Box({
children: [
Widget.Icon(icon),
Widget.Label({
label,
hexpand: true,
xalign: 0,
}),
],
}),
});
export default () =>
Widget.Menu({
class_name: "desktop-menu",
children: [
Widget.MenuItem({
child: Widget.Box({
children: [
Widget.Icon(icons.powermenu.shutdown),
Widget.Label({
label: "System",
hexpand: true,
xalign: 0,
}),
],
}),
submenu: Widget.Menu({
children: [
Item("Shutdown", icons.powermenu.shutdown, () =>
PowerMenu.action("shutdown"),
),
Item("Log Out", icons.powermenu.logout, () =>
PowerMenu.action("logout"),
),
Item("Reboot", icons.powermenu.reboot, () =>
PowerMenu.action("reboot"),
),
Item("Sleep", icons.powermenu.sleep, () =>
PowerMenu.action("reboot"),
),
],
}),
}),
Item("Applications", icons.apps.apps, () =>
App.openWindow("applauncher"),
),
new Gtk.SeparatorMenuItem(),
Item("Settings", icons.ui.settings, openSettings),
],
});

View File

@@ -1,142 +0,0 @@
import App from "resource:///com/github/Aylur/ags/app.js";
import Hyprland from "resource:///com/github/Aylur/ags/service/hyprland.js";
import Applications from "resource:///com/github/Aylur/ags/service/applications.js";
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import icons from "../icons.js";
import options from "../options.js";
import { launchApp, range } from "../utils.js";
const focus = ({ address }) =>
Hyprland.sendMessage(`dispatch focuswindow address:${address}`);
/** @param {import('types/widgets/button').ButtonProps & { icon: string, pinned?: boolean }} o */
const AppButton = ({ icon, pinned = false, ...rest }) => {
const indicators = Widget.Box({
vpack: "end",
hpack: "center",
children: range(5, 0).map(() =>
Widget.Box({
class_name: "indicator",
visible: false,
}),
),
});
return Widget.Button({
...rest,
attribute: indicators,
child: Widget.Box({
class_name: "box",
child: Widget.Overlay({
child: Widget.Icon({
icon,
size: options.desktop.dock.icon_size.bind("value"),
}),
pass_through: true,
overlays: pinned ? [indicators] : [],
}),
}),
});
};
const Taskbar = () =>
Widget.Box({
children: Hyprland.bind("clients").transform((c) =>
c.map((client) => {
for (const appName of options.desktop.dock.pinned_apps.value) {
if (client.class.toLowerCase().includes(appName.toLowerCase()))
return null;
}
for (const app of Applications.list) {
if (
(client.title && app.match(client.title)) ||
(client.class && app.match(client.class))
) {
return AppButton({
icon: app.icon_name || "",
tooltip_text: app.name,
on_primary_click: () => focus(client),
on_middle_click: () => launchApp(app),
});
}
}
}),
),
});
const PinnedApps = () =>
Widget.Box({
class_name: "pins",
homogeneous: true,
children: options.desktop.dock.pinned_apps.bind("value").transform((v) =>
v
.map((term) => ({ app: Applications.query(term)?.[0], term }))
.filter(({ app }) => app)
.map(({ app, term }) =>
AppButton({
pinned: true,
icon: app.icon_name || "",
on_primary_click: () => {
for (const client of Hyprland.clients) {
if (client.class.toLowerCase().includes(term))
return focus(client);
}
launchApp(app);
},
on_middle_click: () => launchApp(app),
tooltip_text: app.name,
setup: (button) =>
button.hook(Hyprland, () => {
const running = Hyprland.clients.filter((client) =>
client.class.toLowerCase().includes(term),
);
const focused = running.find(
(client) => client.address === Hyprland.active.client.address,
);
const index = running.findIndex((c) => c === focused);
for (let i = 0; i < 5; ++i) {
const indicator = button.attribute.children[i];
indicator.visible = i < running.length;
indicator.toggleClassName("focused", i === index);
}
button.set_tooltip_text(
running.length === 1 ? running[0].title : app.name,
);
}),
}),
),
),
});
export default () => {
const pinnedapps = PinnedApps();
const taskbar = Taskbar();
const applauncher = AppButton({
class_name: "launcher nonrunning",
icon: icons.apps.apps,
tooltip_text: "Applications",
on_clicked: () => App.toggleWindow("applauncher"),
});
const separator = Widget.Separator({
vpack: "center",
hpack: "center",
orientation: 1,
setup: (self) =>
self.hook(
taskbar,
() => {
self.visible = taskbar.children.length > 0;
},
"notify::children",
),
});
return Widget.Box({
class_name: "dock",
children: [applauncher, pinnedapps, separator, taskbar],
});
};

View File

@@ -1,44 +0,0 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import Hyprland from "resource:///com/github/Aylur/ags/service/hyprland.js";
import Dock from "./Dock.js";
import options from "../options.js";
/** @param {number} monitor */
export default (monitor) => {
const revealer = Widget.Revealer({
transition: "slide_up",
child: Dock(),
setup: (self) => {
const update = () => {
const ws = Hyprland.getWorkspace(Hyprland.active.workspace.id);
if (Hyprland.getMonitor(monitor)?.name === ws?.monitor)
self.reveal_child = ws?.windows === 0;
};
self
.hook(Hyprland, update, "client-added")
.hook(Hyprland, update, "client-removed")
.hook(Hyprland.active.workspace, update);
},
});
return Widget.Window({
monitor,
name: `dock${monitor}`,
class_name: "floating-dock",
anchor: ["bottom"],
child: Widget.Box({
children: [
revealer,
Widget.Box({
class_name: "padding",
css: "padding: 2px;",
}),
],
}),
setup: (self) =>
self
.on("enter-notify-event", () => (revealer.reveal_child = true))
.on("leave-notify-event", () => (revealer.reveal_child = false))
.bind("visible", options.bar.position, "value", (v) => v !== "bottom"),
});
};

View File

@@ -1,122 +0,0 @@
export default {
lock: "system-lock-screen-symbolic",
fallback: {
executable: "application-x-executable-symbolic",
},
audio: {
mic: {
muted: "microphone-disabled-symbolic",
low: "microphone-sensitivity-low-symbolic",
medium: "microphone-sensitivity-medium-symbolic",
high: "microphone-sensitivity-high-symbolic",
},
volume: {
muted: "audio-volume-muted-symbolic",
low: "audio-volume-low-symbolic",
medium: "audio-volume-medium-symbolic",
high: "audio-volume-high-symbolic",
overamplified: "audio-volume-overamplified-symbolic",
},
type: {
headset: "audio-headphones-symbolic",
speaker: "audio-speakers-symbolic",
card: "audio-card-symbolic",
},
mixer: "",
},
asusctl: {
profile: {
Balanced: "power-profile-balanced-symbolic",
Quiet: "power-profile-power-saver-symbolic",
Performance: "power-profile-performance-symbolic",
},
mode: {
Integrated: "",
Hybrid: "󰢮",
},
},
apps: {
apps: "view-app-grid-symbolic",
search: "folder-saved-search-symbolic",
},
battery: {
charging: "󱐋",
warning: "battery-empty-symbolic",
},
bluetooth: {
enabled: "bluetooth-active-symbolic",
disabled: "bluetooth-disabled-symbolic",
},
brightness: {
indicator: "display-brightness-symbolic",
keyboard: "keyboard-brightness-symbolic",
screen: "display-brightness-symbolic",
},
powermenu: {
sleep: "weather-clear-night-symbolic",
reboot: "system-reboot-symbolic",
logout: "system-log-out-symbolic",
shutdown: "system-shutdown-symbolic",
},
recorder: {
recording: "media-record-symbolic",
},
notifications: {
noisy: "preferences-system-notifications-symbolic",
silent: "notifications-disabled-symbolic",
},
trash: {
full: "user-trash-full-symbolic",
empty: "user-trash-symbolic",
},
mpris: {
fallback: "audio-x-generic-symbolic",
shuffle: {
enabled: "󰒟",
disabled: "󰒟",
},
loop: {
none: "󰓦",
track: "󰓦",
playlist: "󰑐",
},
playing: "󰏦",
paused: "󰐍",
stopped: "󰐍",
prev: "󰒮",
next: "󰒭",
},
ui: {
colorpicker: "color-select-symbolic",
close: "window-close-symbolic",
info: "info-symbolic",
menu: "open-menu-symbolic",
link: "external-link-symbolic",
settings: "emblem-system-symbolic",
tick: "object-select-symbolic",
arrow: {
right: "pan-end-symbolic",
left: "pan-start-symbolic",
down: "pan-down-symbolic",
up: "pan-up-symbolic",
},
},
system: {
cpu: "org.gnome.SystemMonitor-symbolic",
ram: "drive-harddisk-solidstate-symbolic",
temp: "temperature-symbolic",
},
dialog: {
Search: "",
Applauncher: "󰵆",
Bar: "",
Border: "󰃇",
Color: "󰏘",
Desktop: "",
Font: "",
General: "󰒓",
Miscellaneous: "󰠱",
Theme: "󰃟",
Notifications: "󰂚 ",
},
};

View File

@@ -1,63 +0,0 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import Avatar from "../misc/Avatar.js";
import Lockscreen from "../services/lockscreen.js";
import Layer from "gi://GtkLayerShell";
const PasswordEntry = () =>
Widget.Box({
children: [
Widget.Entry({
setup: (self) => self.hook(Lockscreen, () => (self.text = ""), "lock"),
visibility: false,
placeholder_text: "Password",
on_accept: ({ text }) => Lockscreen.auth(text || ""),
hpack: "center",
hexpand: true,
}),
Widget.Spinner({
active: true,
vpack: "center",
setup: (self) =>
self.hook(
Lockscreen,
(_, auth) => (self.visible = auth),
"authenticating",
),
}),
],
});
/** @param {number} monitor */
export default (monitor) => {
const win = Widget.Window({
name: `lockscreen${monitor}`,
class_name: "lockscreen",
monitor,
layer: "overlay",
visible: false,
setup: (self) =>
self.hook(Lockscreen, (_, lock) => (self.visible = lock), "lock"),
child: Widget.Box({
css: "min-width: 3000px; min-height: 2000px;",
class_name: "shader",
child: Widget.Box({
class_name: "content",
vertical: true,
hexpand: true,
vexpand: true,
hpack: "center",
vpack: "center",
children: [
Avatar({
hpack: "center",
vpack: "center",
}),
PasswordEntry(),
],
}),
}),
});
Layer.set_keyboard_mode(win, Layer.KeyboardMode.EXCLUSIVE);
return win;
};

View File

@@ -1,7 +0,0 @@
#! /usr/bin/env python
import pam
import sys
import getpass
print(pam.authenticate(getpass.getuser(), sys.argv[1]));

View File

@@ -1,43 +0,0 @@
import Dashboard from "./dashboard/Dashboard.js";
import Desktop from "./desktop/Desktop.js";
import Lockscreen from "./lockscreen/Lockscreen.js";
import Notifications from "./notifications/Notifications.js";
import OSD from "./osd/OSD.js";
import Overview from "./overview/Overview.js";
import PowerMenu from "./powermenu/PowerMenu.js";
import QuickSettings from "./quicksettings/QuickSettings.js";
import ScreenCorners from "./screencorner/ScreenCorners.js";
import TopBar from "./bar/TopBar.js";
import Verification from "./powermenu/Verification.js";
import About from "./about/about.js";
import { init } from "./settings/setup.js";
import { forMonitors } from "./utils.js";
import { initWallpaper } from "./settings/wallpaper.js";
import options from "./options.js";
initWallpaper();
const windows = () => [
forMonitors(Desktop),
forMonitors(Lockscreen),
forMonitors(Notifications),
forMonitors(OSD),
forMonitors(ScreenCorners),
forMonitors(TopBar),
Dashboard(),
Overview(),
PowerMenu(),
QuickSettings(),
Verification(),
About(),
];
export default {
onConfigParsed: init,
windows: windows().flat(1),
closeWindowDelay: {
quicksettings: options.transition.value,
dashboard: options.transition.value,
},
};

View File

@@ -1,16 +0,0 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import options from "../options.js";
/** @param {import('types/widgets/box').BoxProps=} props */
export default (props) =>
Widget.Box({ ...props, class_name: "avatar" })
.hook(options.desktop.avatar, (box) =>
box.setCss(`
background-image: url('${options.desktop.avatar.value}');
background-size: cover;
`),
)
.on("size-allocate", (box) => {
const h = box.get_allocated_height();
box.set_size_request(Math.ceil(h * 1.1), -1);
});

View File

@@ -1,14 +0,0 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import Battery from "resource:///com/github/Aylur/ags/service/battery.js";
export default () =>
Widget.Icon({
class_name: "battery",
icon: Battery.bind("icon_name"),
setup: (icon) =>
icon.hook(Battery, () => {
icon.toggleClassName("charging", Battery.charging);
icon.toggleClassName("charged", Battery.charged);
icon.toggleClassName("low", Battery.percent < 30);
}),
});

View File

@@ -1,17 +0,0 @@
import { clock } from "../variables.js";
import Widget from "resource:///com/github/Aylur/ags/widget.js";
/**
* @param {import('types/widgets/label').Props & {
* format?: string,
* interval?: number,
* }} o
*/
export default ({ format = "%H:%M:%S %B %e. %A", ...rest } = {}) =>
Widget.Label({
class_name: "clock",
label: clock.bind("value").transform((time) => {
return time.format(format) || "wrong format";
}),
...rest,
});

View File

@@ -1,48 +0,0 @@
import Gtk from "gi://Gtk?version=3.0";
import { subclass, register } from "resource:///com/github/Aylur/ags/widget.js";
import AgsLabel from "resource:///com/github/Aylur/ags/widgets/label.js";
class FontIcon extends AgsLabel {
static {
register(this);
}
/** @param {string | import('types/widgets/label').Props<any> & { icon?: string }} params */
constructor(params = "") {
// @ts-expect-error
const { icon = "", ...rest } = params;
super(typeof params === "string" ? {} : rest);
this.toggleClassName("font-icon");
if (typeof params === "object") this.icon = icon;
if (typeof params === "string") this.icon = params;
}
get icon() {
return this.label;
}
set icon(icon) {
this.label = icon;
}
get size() {
return this.get_style_context().get_property(
"font-size",
Gtk.StateFlags.NORMAL,
);
}
/** @returns {[number, number]} */
vfunc_get_preferred_height() {
return [this.size, this.size];
}
/** @returns {[number, number]} */
vfunc_get_preferred_width() {
return [this.size, this.size];
}
}
export default subclass(FontIcon);

View File

@@ -1,62 +0,0 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
/**
* @typedef {import('types/widgets/eventbox').EventBoxProps & {
* indicator?: import('types/widgets/box').BoxProps['child']
* direction?: 'left' | 'right' | 'down' | 'up'
* duration?: number
* setupRevealer?: (rev: ReturnType<typeof Widget.Revealer>) => void
* setupEventBox?: (rev: ReturnType<typeof Widget.EventBox>) => void
* }} HoverRevealProps
*/
/**
* @param {HoverRevealProps} props
*/
export default ({
indicator,
child,
direction = "left",
duration = 300,
setupEventBox,
setupRevealer,
...rest
}) => {
let open = false;
const vertical = direction === "down" || direction === "up";
const posStart = direction === "down" || direction === "right";
const posEnd = direction === "up" || direction === "left";
const revealer = Widget.Revealer({
transition: `slide_${direction}`,
setup: setupRevealer,
transition_duration: duration,
child,
});
const eventbox = Widget.EventBox({
...rest,
setup: setupEventBox,
on_hover: () => {
if (open) return;
revealer.reveal_child = true;
Utils.timeout(duration, () => (open = true));
},
on_hover_lost: () => {
if (!open) return;
revealer.reveal_child = false;
open = false;
},
child: Widget.Box({
vertical,
children: [posStart && indicator, revealer, posEnd && indicator],
}),
});
return Widget.Box({
children: [eventbox],
});
};

View File

@@ -1,63 +0,0 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import RegularWindow from "./RegularWindow.js";
import Gtk from "gi://Gtk";
export default () => {
const selected = Widget.Label({
css: "font-size: 1.2em;",
});
const flowbox = Widget.FlowBox({
min_children_per_line: 10,
setup: (self) => {
self.connect("child-activated", (_, child) => {
selected.label = child.get_child().iconName;
});
Gtk.IconTheme.get_default()
.list_icons(null)
.sort()
.map((icon) => {
!icon.endsWith(".symbolic") &&
self.insert(
Widget.Icon({
icon,
size: 38,
}),
-1,
);
});
self.show_all();
},
});
const entry = Widget.Entry({
on_change: ({ text }) =>
flowbox.get_children().forEach((child) => {
child.visible = child.get_child().iconName.includes(text);
}),
});
return RegularWindow({
name: "icons",
visible: true,
child: Widget.Box({
css: "padding: 30px;",
spacing: 20,
vertical: true,
children: [
entry,
Widget.Scrollable({
hscroll: "never",
vscroll: "always",
hexpand: true,
vexpand: true,
css: "min-width: 500px;" + "min-height: 500px;",
child: flowbox,
}),
selected,
],
}),
});
};

View File

@@ -1,132 +0,0 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import GLib from "gi://GLib";
/** @param {import('types/service/notifications').Notification} n */
const NotificationIcon = ({ app_entry, app_icon, image }) => {
if (image) {
return Widget.Box({
vpack: "start",
hexpand: false,
class_name: "icon img",
css: `
background-image: url("${image}");
background-size: cover;
background-repeat: no-repeat;
background-position: center;
min-width: 78px;
min-height: 78px;
`,
});
}
let icon = "dialog-information-symbolic";
if (Utils.lookUpIcon(app_icon)) icon = app_icon;
if (Utils.lookUpIcon(app_entry || "")) icon = app_entry || "";
return Widget.Box({
vpack: "start",
hexpand: false,
class_name: "icon",
css: `
min-width: 78px;
min-height: 78px;
`,
child: Widget.Icon({
icon,
size: 58,
hpack: "center",
hexpand: true,
vpack: "center",
vexpand: true,
}),
});
};
/** @param {import('types/service/notifications').Notification} notification */
export default (notification) => {
const content = Widget.Box({
class_name: "content",
children: [
NotificationIcon(notification),
Widget.Box({
hexpand: true,
vertical: true,
children: [
Widget.Box({
children: [
Widget.Label({
class_name: "title",
xalign: 0,
justification: "left",
hexpand: true,
max_width_chars: 24,
truncate: "end",
wrap: true,
label: notification.summary,
use_markup: true,
}),
Widget.Label({
class_name: "time",
vpack: "start",
label: GLib.DateTime.new_from_unix_local(
notification.time,
).format("%H:%M"),
}),
Widget.Button({
class_name: "close-button",
vpack: "start",
child: Widget.Icon("window-close-symbolic"),
on_clicked: () => notification.close(),
}),
],
}),
Widget.Label({
class_name: "description",
hexpand: true,
use_markup: true,
xalign: 0,
justification: "left",
label: notification.body,
wrap: true,
}),
],
}),
],
});
const actionsbox = Widget.Revealer({
transition: "slide_down",
child: Widget.EventBox({
child: Widget.Box({
class_name: "actions horizontal",
children: notification.actions.map((action) =>
Widget.Button({
class_name: "action-button",
on_clicked: () => notification.invoke(action.id),
hexpand: true,
child: Widget.Label(action.label),
}),
),
}),
}),
});
return Widget.EventBox({
class_name: `notification ${notification.urgency}`,
vexpand: false,
on_primary_click: () => notification.dismiss(),
on_hover() {
actionsbox.reveal_child = true;
},
on_hover_lost() {
actionsbox.reveal_child = true;
notification.dismiss();
},
child: Widget.Box({
vertical: true,
children: [content, notification.actions.length > 0 && actionsbox],
}),
});
};

View File

@@ -1,83 +0,0 @@
import AgsWindow from "resource:///com/github/Aylur/ags/widgets/window.js";
import App from "resource:///com/github/Aylur/ags/app.js";
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import options from "../options.js";
import GObject from "gi://GObject";
const keyGrabber = Widget.Window({
name: "key-grabber",
popup: true,
anchor: ["top", "left", "right", "bottom"],
css: "background-color: transparent;",
visible: false,
exclusivity: "ignore",
keymode: "on-demand",
layer: "top",
attribute: { list: [] },
setup: (self) =>
self.on("notify::visible", ({ visible }) => {
if (!visible)
self.attribute?.list.forEach((name) => App.closeWindow(name));
}),
child: Widget.EventBox({ vexpand: true }).on("button-press-event", () => {
App.closeWindow("key-grabber");
keyGrabber.attribute?.list.forEach((name) => App.closeWindow(name));
}),
});
// add before any PopupWindow is instantiated
App.addWindow(keyGrabber);
export class PopupWindow extends AgsWindow {
static {
GObject.registerClass(this);
}
constructor({ name, child, transition = "none", visible = false, ...rest }) {
super({
...rest,
name,
popup: true,
keymode: "exclusive",
layer: "overlay",
class_names: ["popup-window", name],
});
child.toggleClassName("window-content");
this.revealer = Widget.Revealer({
transition,
child,
transition_duration: options.transition.value,
setup: (self) =>
self.hook(App, (_, wname, visible) => {
if (wname === name) this.revealer.reveal_child = visible;
}),
});
this.child = Widget.Box({
css: "padding: 1px;",
child: this.revealer,
});
this.show_all();
this.visible = visible;
keyGrabber.bind("visible", this, "visible");
keyGrabber.attribute?.list.push(name);
}
set transition(dir) {
this.revealer.transition = dir;
}
get transition() {
return this.revealer.transition;
}
}
/** @param {import('types/widgets/window').WindowProps & {
* name: string
* child: import('types/widgets/box').default
* transition?: import('types/widgets/revealer').RevealerProps['transition']
* }} config
*/
export default (config) => new PopupWindow(config);

View File

@@ -1,60 +0,0 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
/** @param {import('types/widgets/box').BoxProps & {
* width: number
* height: number
* }} o */
export default ({
height = 18,
width = 180,
vertical = false,
child,
...props
}) => {
const fill = Widget.Box({
class_name: "fill",
hexpand: vertical,
vexpand: !vertical,
hpack: vertical ? "fill" : "start",
vpack: vertical ? "end" : "fill",
children: [child],
});
let fill_size = 0;
return Widget.Box({
...props,
class_name: "progress",
css: `
min-width: ${width}px;
min-height: ${height}px;
`,
children: [fill],
attribute: (value) => {
if (value < 0) return;
const axis = vertical ? "height" : "width";
const axisv = vertical ? height : width;
const min = vertical ? width : height;
const preferred = (axisv - min) * value + min;
if (!fill_size) {
fill_size = preferred;
fill.setCss(`min-${axis}: ${preferred}px;`);
return;
}
const frames = 10;
const goal = preferred - fill_size;
const step = goal / frames;
for (let i = 0; i < frames; ++i) {
Utils.timeout(5 * i, () => {
fill_size += step;
fill.setCss(`min-${axis}: ${fill_size}px`);
});
}
},
});
};

View File

@@ -1,4 +0,0 @@
import { subclass } from "resource:///com/github/Aylur/ags/widget.js";
import Gtk from "gi://Gtk";
export default subclass(Gtk.Window, "RegularWindow");

View File

@@ -1,263 +0,0 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import icons from "../icons.js";
import { blurImg } from "../utils.js";
/**
* @param {import('types/service/mpris').MprisPlayer} player
* @param {import('types/widgets/box').BoxProps=} props
*/
export const CoverArt = (player, props) =>
Widget.Box({
...props,
class_name: "cover",
css: player
.bind("cover_path")
.transform((p) => `background-image: url("${p}")`),
});
/**
* @param {import('types/service/mpris').MprisPlayer} player
* @param {import('types/widgets/box').BoxProps=} props
*/
export const BlurredCoverArt = (player, props) =>
Widget.Box({
...props,
class_name: "blurred-cover",
setup: (self) =>
self.hook(
player,
(box) =>
blurImg(player.cover_path).then((img) => {
img && box.setCss(`background-image: url("${img}")`);
}),
"notify::cover-path",
),
});
/**
* @param {import('types/service/mpris').MprisPlayer} player
* @param {import('types/widgets/label').Props=} props
*/
export const TitleLabel = (player, props) =>
Widget.Label({
...props,
class_name: "title",
label: player.bind("track_title"),
});
/**
* @param {import('types/service/mpris').MprisPlayer} player
* @param {import('types/widgets/label').Props=} props
*/
export const ArtistLabel = (player, props) =>
Widget.Label({
...props,
class_name: "artist",
label: player.bind("track_artists").transform((a) => a.join(", ") || ""),
});
/**
* @param {import('types/service/mpris').MprisPlayer} player
* @param {import('types/widgets/icon').Props & { symbolic?: boolean }=} props
*/
export const PlayerIcon = (player, { symbolic = true, ...props } = {}) =>
Widget.Icon({
...props,
class_name: "player-icon",
tooltip_text: player.identity || "",
setup: (self) =>
self.hook(player, (icon) => {
const name = `${player.entry}${symbolic ? "-symbolic" : ""}`;
Utils.lookUpIcon(name)
? (icon.icon = name)
: (icon.icon = icons.mpris.fallback);
}),
});
/**
* @param {import('types/service/mpris').MprisPlayer} player
* @param {import('types/widgets/slider').SliderProps=} props
*/
export const PositionSlider = (player, props) =>
Widget.Slider({
...props,
class_name: "position-slider",
draw_value: false,
on_change: ({ value }) => (player.position = player.length * value),
setup: (self) => {
const update = () => {
if (self.dragging) return;
self.visible = player.length > 0;
if (player.length > 0) self.value = player.position / player.length;
};
self.hook(player, update);
self.hook(player, update, "position");
self.poll(1000, update);
},
});
/** @param {number} length */
function lengthStr(length) {
const min = Math.floor(length / 60);
const sec = Math.floor(length % 60);
const sec0 = sec < 10 ? "0" : "";
return `${min}:${sec0}${sec}`;
}
/** @param {import('types/service/mpris').MprisPlayer} player */
export const PositionLabel = (player) =>
Widget.Label({
setup: (self) => {
const update = (_, time) => {
player.length > 0
? (self.label = lengthStr(time || player.position))
: (self.visible = !!player);
};
self.hook(player, update, "position");
self.poll(1000, update);
},
});
/** @param {import('types/service/mpris').MprisPlayer} player */
export const LengthLabel = (player) =>
Widget.Label({
label: player.bind("length").transform((l) => lengthStr(l)),
visible: player.bind("length").transform((l) => l > 0),
});
/** @param {import('types/service/mpris').MprisPlayer} player */
export const Slash = (player) =>
Widget.Label({
label: "/",
visible: player.bind("length").transform((l) => l > 0),
});
/**
* @param {Object} o
* @param {import('types/service/mpris').MprisPlayer} o.player
* @param {import('types/widgets/stack').StackProps['children']} o.children
* @param {'shuffle' | 'loop' | 'playPause' | 'previous' | 'next'} o.onClick
* @param {string} o.prop
* @param {string} o.canProp
* @param {any} o.cantValue
*/
const PlayerButton = ({
player,
children,
onClick,
prop,
canProp,
cantValue,
}) =>
Widget.Button({
child: Widget.Stack({ children }).bind(
"shown",
player,
prop,
(p) => `${p}`,
),
on_clicked: () => player[onClick](),
visible: player.bind(canProp).transform((c) => c !== cantValue),
});
/** @param {import('types/service/mpris').MprisPlayer} player */
export const ShuffleButton = (player) =>
PlayerButton({
player,
children: {
true: Widget.Label({
class_name: "shuffle enabled",
label: icons.mpris.shuffle.enabled,
}),
false: Widget.Label({
class_name: "shuffle disabled",
label: icons.mpris.shuffle.disabled,
}),
},
onClick: "shuffle",
prop: "shuffle-status",
canProp: "shuffle-status",
cantValue: null,
});
/** @param {import('types/service/mpris').MprisPlayer} player */
export const LoopButton = (player) =>
PlayerButton({
player,
children: {
None: Widget.Label({
class_name: "loop none",
label: icons.mpris.loop.none,
}),
Track: Widget.Label({
class_name: "loop track",
label: icons.mpris.loop.track,
}),
Playlist: Widget.Label({
class_name: "loop playlist",
label: icons.mpris.loop.playlist,
}),
},
onClick: "loop",
prop: "loop-status",
canProp: "loop-status",
cantValue: null,
});
/** @param {import('types/service/mpris').MprisPlayer} player */
export const PlayPauseButton = (player) =>
PlayerButton({
player,
children: {
Playing: Widget.Label({
class_name: "playing",
label: icons.mpris.playing,
}),
Paused: Widget.Label({
class_name: "paused",
label: icons.mpris.paused,
}),
Stopped: Widget.Label({
class_name: "stopped",
label: icons.mpris.stopped,
}),
},
onClick: "playPause",
prop: "play-back-status",
canProp: "can-play",
cantValue: false,
});
/** @param {import('types/service/mpris').MprisPlayer} player */
export const PreviousButton = (player) =>
PlayerButton({
player,
children: {
true: Widget.Label({
class_name: "previous",
label: icons.mpris.prev,
}),
},
onClick: "previous",
prop: "can-go-prev",
canProp: "can-go-prev",
cantValue: false,
});
/** @param {import('types/service/mpris').MprisPlayer} player */
export const NextButton = (player) =>
PlayerButton({
player,
children: {
true: Widget.Label({
class_name: "next",
label: icons.mpris.next,
}),
},
onClick: "next",
prop: "can-go-next",
canProp: "can-go-next",
cantValue: false,
});

View File

@@ -1,68 +0,0 @@
import Notifications from "resource:///com/github/Aylur/ags/service/notifications.js";
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import Notification from "../misc/Notification.js";
import options from "../options.js";
/** @param {import('types/widgets/revealer').default} parent */
const Popups = (parent) => {
const map = new Map();
const onDismissed = (_, id, force = false) => {
if (!id || !map.has(id)) return;
if (map.get(id).isHovered() && !force) return;
if (map.size - 1 === 0) parent.reveal_child = false;
Utils.timeout(200, () => {
map.get(id)?.destroy();
map.delete(id);
});
};
/** @param {import('types/widgets/box').default} box */
const onNotified = (box, id) => {
if (!id || Notifications.dnd) return;
const n = Notifications.getNotification(id);
if (!n) return;
if (options.notifications.black_list.value.includes(n.app_name || ""))
return;
map.delete(id);
map.set(id, Notification(n));
box.children = Array.from(map.values()).reverse();
Utils.timeout(10, () => {
parent.reveal_child = true;
});
};
return Widget.Box({ vertical: true })
.hook(Notifications, onNotified, "notified")
.hook(Notifications, onDismissed, "dismissed")
.hook(Notifications, (box, id) => onDismissed(box, id, true), "closed");
};
/** @param {import('types/widgets/revealer').RevealerProps['transition']} transition */
const PopupList = (transition = "slide_down") =>
Widget.Box({
css: "padding: 1px",
children: [
Widget.Revealer({
transition,
setup: (self) => (self.child = Popups(self)),
}),
],
});
/** @param {number} monitor */
export default (monitor) =>
Widget.Window({
monitor,
name: `notifications${monitor}`,
class_name: "notifications",
anchor: options.notifications.position.bind("value"),
child: PopupList(),
});

View File

@@ -1,303 +0,0 @@
/**
* An object holding Options that are Variables with cached values.
*
* to update an option at runtime simply run
* ags -r "options.path.to.option.setValue('value')"
*
* resetting:
* ags -r "options.reset()"
*/
import {
Option,
resetOptions,
getValues,
apply,
getOptions,
} from "./settings/option.js";
import { USER } from "resource:///com/github/Aylur/ags/utils.js";
import themes from "./themes.js";
export default {
reset: resetOptions,
values: getValues,
apply: apply,
list: getOptions,
spacing: Option(9),
padding: Option(8),
radii: Option(9),
popover_padding_multiplier: Option(1.4, {
category: "General",
note: "popover-padding: padding × this",
type: "float",
unit: "",
}),
color: {
red: Option("#e55f86", { scss: "red" }),
green: Option("#00D787", { scss: "green" }),
yellow: Option("#EBFF71", { scss: "yellow" }),
blue: Option("#51a4e7", { scss: "blue" }),
magenta: Option("#9077e7", { scss: "magenta" }),
teal: Option("#51e6e6", { scss: "teal" }),
orange: Option("#E79E64", { scss: "orange" }),
},
theme: {
name: Option(themes[0].name, {
category: "exclude",
note: "Name to show as active in quicktoggles",
}),
icon: Option(themes[0].icon, {
category: "exclude",
note: "Icon to show as active in quicktoggles",
}),
scheme: Option("dark", {
enums: ["dark", "light"],
type: "enum",
note: "Color scheme to set on Gtk apps: 'ligth' or 'dark'",
title: "Color Scheme",
scss: "color-scheme",
}),
bg: Option("#171717", {
title: "Background Color",
scss: "bg-color",
}),
fg: Option("#eeeeee", {
title: "Foreground Color",
scss: "fg-color",
}),
accent: {
accent: Option("$blue", {
category: "Theme",
title: "Accent Color",
scss: "accent",
}),
fg: Option("#141414", {
category: "Theme",
title: "Accent Foreground Color",
scss: "accent-fg",
}),
gradient: Option("to right, $accent, lighten($accent, 6%)", {
category: "Theme",
title: "Accent Linear Gradient",
scss: "accent-gradient",
}),
},
widget: {
bg: Option("$fg-color", {
category: "Theme",
title: "Widget Background Color",
scss: "_widget-bg",
}),
opacity: Option(94, {
category: "Theme",
title: "Widget Background Opacity",
unit: "",
scss: "widget-opacity",
}),
},
},
border: {
color: Option("$fg-color", {
category: "Border",
title: "Border Color",
scss: "_border-color",
}),
opacity: Option(97, {
category: "Border",
title: "Border Opacity",
unit: "",
}),
width: Option(1, {
category: "Border",
title: "Border Width",
}),
},
hypr: {
inactive_border: Option("rgba(333333ff)", {
category: "Border",
title: "Border on Inactive Windows",
scss: "exclude",
}),
wm_gaps_multiplier: Option(2.4, {
category: "General",
scss: "wm-gaps-multiplier",
note: "wm-gaps: padding × this",
type: "float",
unit: "",
}),
},
// TODO: use this on revealers
transition: Option(200, {
category: "exclude",
note: "Transition time on aminations in ms, e.g on hover",
unit: "ms",
}),
font: {
font: Option("Ubuntu Nerd Font", {
type: "font",
title: "Font",
scss: "font",
}),
mono: Option("Mononoki Nerd Font", {
title: "Monospaced Font",
scss: "mono-font",
}),
size: Option(13, {
scss: "font-size",
unit: "pt",
}),
},
applauncher: {
width: Option(500),
height: Option(500),
icon_size: Option(52),
},
bar: {
position: Option("top", {
enums: ["top", "bottom"],
type: "enum",
}),
style: Option("normal", {
enums: ["floating", "normal", "separated"],
type: "enum",
}),
flat_buttons: Option(true, { scss: "bar-flat-buttons" }),
separators: Option(true),
icon: Option("distro-icon", {
note: '"distro-icon" or a single font',
}),
},
battery: {
show_percentage: Option(true, {
persist: true,
noReload: false,
category: "exclude",
}),
bar: {
show_icon: Option(true, { category: "Bar" }),
width: Option(70, { category: "Bar" }),
height: Option(14, { category: "Bar" }),
full: Option(false, { category: "Bar" }),
},
low: Option(30, { category: "Bar" }),
medium: Option(50, { category: "Bar" }),
},
desktop: {
wallpaper: {
fg: Option("#fff", { scss: "wallpaper-fg" }),
img: Option(themes[0].options["desktop.wallpaper.img"], {
scssFormat: (v) => `"${v}"`,
type: "img",
}),
},
avatar: Option(`/var/lib/AccountsService/icons/${USER}`, {
scssFormat: (v) => `"${v}"`,
type: "img",
note: "displayed in quicksettings and locksreen",
}),
screen_corners: Option(true, { scss: "screen-corners" }),
clock: {
enable: Option(true),
position: Option("center center", {
note: "halign valign",
}),
},
drop_shadow: Option(true, { scss: "drop-shadow" }),
shadow: Option("rgba(0, 0, 0, .6)", { scss: "shadow" }),
dock: {
icon_size: Option(56),
pinned_apps: Option(
[
"firefox",
"org.wezfurlong.wezterm",
"org.gnome.Nautilus",
"org.gnome.Calendar",
"obsidian",
"transmission-gtk",
"caprine",
"teams-for-linux",
"discord",
"spotify",
"com.usebottles.bottles",
"org.gnome.Software",
],
{ scss: "exclude" },
),
},
},
notifications: {
black_list: Option(["Spotify"], { note: "app-name | entry" }),
position: Option(["top"], { note: "anchor" }),
width: Option(450),
},
dashboard: {
sys_info_size: Option(70, {
category: "Desktop",
scss: "sys-info-size",
}),
},
mpris: {
black_list: Option(["Caprine"], {
category: "Bar",
title: "List of blacklisted mpris players",
note: "filters for bus-name, name, identity, entry",
}),
preferred: Option("spotify", {
category: "Bar",
title: "Preferred player",
}),
},
workspaces: Option(10, {
category: "Bar",
title: "No. workspaces on bar and overview",
note: "Set it to 0 to make it dynamic",
}),
temperature: "/sys/class/thermal/thermal_zone0/temp",
systemFetchInterval: 5000,
brightnessctlKBD: "asus::kbd_backlight",
substitutions: {
icons: [
["transmission-gtk", "transmission"],
["blueberry.py", "bluetooth"],
["Caprine", "facebook-messenger"],
["", "preferences-desktop-display"],
],
titles: [
["com.github.Aylur.ags", "AGS"],
["transmission-gtk", "Transmission"],
["com.obsproject.Studio", "OBS"],
["com.usebottles.bottles", "Bottles"],
["com.github.wwmm.easyeffects", "Easy Effects"],
["org.gnome.TextEditor", "Text Editor"],
["org.gnome.design.IconLibrary", "Icon Library"],
["blueberry.py", "Blueberry"],
["org.wezfurlong.wezterm", "Wezterm"],
["com.raggesilver.BlackBox", "BlackBox"],
["firefox", "Firefox"],
["org.gnome.Nautilus", "Files"],
["libreoffice-writer", "Writer"],
["", "Desktop"],
],
},
};

View File

@@ -1,60 +0,0 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import FontIcon from "../misc/FontIcon.js";
import Progress from "../misc/Progress.js";
import Indicator from "../services/onScreenIndicator.js";
export const OnScreenIndicator = ({ height = 300, width = 48 } = {}) =>
Widget.Box({
class_name: "indicator",
css: "padding: 1px;",
child: Widget.Revealer({
transition: "slide_left",
setup: (self) =>
self.hook(Indicator, (_, value) => {
self.reveal_child = value > -1;
}),
child: Progress({
width,
height,
vertical: true,
setup: (self) =>
self.hook(Indicator, (_, value) => self.attribute(value)),
child: Widget.Stack({
vpack: "start",
hpack: "center",
hexpand: false,
children: {
true: Widget.Icon({
hpack: "center",
size: width,
setup: (w) =>
w.hook(Indicator, (_, _v, name) => (w.icon = name || "")),
}),
false: FontIcon({
hpack: "center",
hexpand: true,
css: `font-size: ${width}px;`,
setup: (w) =>
w.hook(Indicator, (_, _v, name) => (w.label = name || "")),
}),
},
setup: (self) =>
self.hook(Indicator, (_, _v, name) => {
self.shown = `${!!Utils.lookUpIcon(name)}`;
}),
}),
}),
}),
});
/** @param {number} monitor */
export default (monitor) =>
Widget.Window({
name: `indicator${monitor}`,
monitor,
class_name: "indicator",
layer: "overlay",
anchor: ["right"],
child: OnScreenIndicator(),
});

View File

@@ -1,57 +0,0 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import App from "resource:///com/github/Aylur/ags/app.js";
import { createSurfaceFromWidget, substitute } from "../utils.js";
import Gdk from "gi://Gdk";
import Gtk from "gi://Gtk?version=3.0";
import options from "../options.js";
import Hyprland from "resource:///com/github/Aylur/ags/service/hyprland.js";
import icons from "../icons.js";
const SCALE = 0.08;
const TARGET = [Gtk.TargetEntry.new("text/plain", Gtk.TargetFlags.SAME_APP, 0)];
/** @param {string} args */
const dispatch = (args) => Hyprland.sendMessage(`dispatch ${args}`);
/** @param {string} str */
const icon = (str) => {
const icon = substitute(options.substitutions.icons, str);
if (Utils.lookUpIcon(icon)) return icon;
console.warn("no icon", icon);
return icons.fallback.executable;
};
export default ({ address, size: [w, h], class: c, title }) =>
Widget.Button({
class_name: "client",
tooltip_text: `${title}`,
child: Widget.Icon({
css: `
min-width: ${w * SCALE}px;
min-height: ${h * SCALE}px;
`,
icon: icon(c),
}),
on_secondary_click: () => dispatch(`closewindow address:${address}`),
on_clicked: () =>
dispatch(`focuswindow address:${address}`).then(() =>
App.closeWindow("overview"),
),
setup: (btn) =>
btn
.on("drag-data-get", (_w, _c, data) =>
data.set_text(address, address.length),
)
.on("drag-begin", (_, context) => {
Gtk.drag_set_icon_surface(context, createSurfaceFromWidget(btn));
btn.toggleClassName("hidden", true);
})
.on("drag-end", () => btn.toggleClassName("hidden", false))
.drag_source_set(
Gdk.ModifierType.BUTTON1_MASK,
TARGET,
Gdk.DragAction.COPY,
),
});

View File

@@ -1,52 +0,0 @@
import Hyprland from "resource:///com/github/Aylur/ags/service/hyprland.js";
import PopupWindow from "../misc/PopupWindow.js";
import Workspace from "./Workspace.js";
import options from "../options.js";
import { range } from "../utils.js";
const ws = options.workspaces;
const Overview = () =>
Widget.Box({
children: [Workspace(0)], // for type infer
setup: (self) =>
Utils.idle(() => {
self.hook(ws, () => {
self.children = range(ws.value).map(Workspace);
update(self);
children(self);
});
self.hook(Hyprland, update);
self.hook(Hyprland, children, "notify::workspaces");
update(self);
children(self);
}),
});
/** @param {ReturnType<typeof Overview>} box */
const update = (box) => {
if (!box.get_parent()?.visible) return;
Hyprland.sendMessage("j/clients")
.then((clients) => {
box.children.forEach((ws) => {
ws.attribute(JSON.parse(clients));
});
})
.catch(console.error);
};
/** @param {import('types/widgets/box').default} box */
const children = (box) => {
if (ws.value === 0) {
box.children = Hyprland.workspaces
.sort((ws1, ws2) => ws1.id - ws2.id)
.map(({ id }) => Workspace(id));
}
};
export default () =>
PopupWindow({
name: "overview",
child: Overview(),
});

View File

@@ -1,60 +0,0 @@
import Hyprland from "resource:///com/github/Aylur/ags/service/hyprland.js";
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import Gdk from "gi://Gdk";
import Gtk from "gi://Gtk?version=3.0";
import Client from "./Client.js";
const SCALE = 0.08;
const TARGET = [Gtk.TargetEntry.new("text/plain", Gtk.TargetFlags.SAME_APP, 0)];
/** @param {string} args */
const dispatch = (args) => Utils.execAsync(`hyprctl dispatch ${args}`);
/** @param {number} index */
export default (index) => {
const fixed = Gtk.Fixed.new();
return Widget.Box({
class_name: "workspace",
vpack: "center",
css: `
min-width: ${3840 * SCALE}px;
min-height: ${2160 * SCALE}px;
`,
setup: (box) =>
box.hook(Hyprland, () => {
box.toggleClassName("active", Hyprland.active.workspace.id === index);
}),
child: Widget.EventBox({
hexpand: true,
vexpand: true,
on_primary_click: () => dispatch(`workspace ${index}`),
setup: (eventbox) => {
eventbox.drag_dest_set(
Gtk.DestDefaults.ALL,
TARGET,
Gdk.DragAction.COPY,
);
eventbox.connect("drag-data-received", (_w, _c, _x, _y, data) => {
dispatch(`movetoworkspacesilent ${index},address:${data.get_text()}`);
});
},
child: fixed,
}),
/** @param {Array<import('types/service/hyprland').Client>} clients */
attribute: (clients) => {
fixed.get_children().forEach((ch) => ch.destroy());
clients
.filter(({ workspace: { id } }) => id === index)
.forEach((c) => {
c.at[0] -= Hyprland.getMonitor(c.monitor)?.x || 0;
c.at[1] -= Hyprland.getMonitor(c.monitor)?.y || 0;
c.mapped && fixed.put(Client(c), c.at[0] * SCALE, c.at[1] * SCALE);
});
fixed.show_all();
},
});
};

View File

@@ -1,31 +0,0 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import icons from "../icons.js";
import PowerMenu from "../services/powermenu.js";
import ShadedPopup from "./ShadedPopup.js";
/**
* @param {'sleep' | 'reboot' | 'logout' | 'shutdown'} action
* @param {string} label
*/
const SysButton = (action, label) =>
Widget.Button({
on_clicked: () => PowerMenu.action(action),
child: Widget.Box({
vertical: true,
children: [Widget.Icon(icons.powermenu[action]), Widget.Label(label)],
}),
});
export default () =>
ShadedPopup({
name: "powermenu",
expand: true,
child: Widget.Box({
children: [
SysButton("sleep", "Sleep"),
SysButton("reboot", "Reboot"),
SysButton("logout", "Log Out"),
SysButton("shutdown", "Shutdown"),
],
}),
});

View File

@@ -1,44 +0,0 @@
import App from "resource:///com/github/Aylur/ags/app.js";
import Widget from "resource:///com/github/Aylur/ags/widget.js";
/** @param {string} windowName */
const Padding = (windowName) =>
Widget.EventBox({
class_name: "padding",
hexpand: true,
vexpand: true,
setup: (w) =>
w.on("button-press-event", () => App.toggleWindow(windowName)),
});
/**
* @template {import('gi://Gtk?version=3.0').default.Widget} T
* @param {import('types/widgets/window').WindowProps<T> & {
* name: string
* child: import('types/widgets/box').default
* }} o
*/
export default ({ name, child, ...rest }) =>
Widget.Window({
...rest,
class_names: ["popup-window", name],
name,
visible: false,
popup: true,
keymode: "on-demand",
setup() {
child.toggleClassName("window-content");
},
child: Widget.CenterBox({
class_name: "shader",
css: "min-width: 5000px; min-height: 3000px;",
start_widget: Padding(name),
end_widget: Padding(name),
center_widget: Widget.CenterBox({
vertical: true,
start_widget: Padding(name),
end_widget: Padding(name),
center_widget: child,
}),
}),
});

View File

@@ -1,46 +0,0 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import App from "resource:///com/github/Aylur/ags/app.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import PowerMenu from "../services/powermenu.js";
import ShadedPopup from "./ShadedPopup.js";
export default () =>
ShadedPopup({
name: "verification",
expand: true,
child: Widget.Box({
vertical: true,
children: [
Widget.Box({
class_name: "text-box",
vertical: true,
children: [
Widget.Label({
class_name: "title",
label: PowerMenu.bind("title"),
}),
Widget.Label({
class_name: "desc",
label: "Are you sure?",
}),
],
}),
Widget.Box({
class_name: "buttons horizontal",
vexpand: true,
vpack: "end",
homogeneous: true,
children: [
Widget.Button({
child: Widget.Label("No"),
on_clicked: () => App.toggleWindow("verification"),
}),
Widget.Button({
child: Widget.Label("Yes"),
on_clicked: () => Utils.exec(PowerMenu.cmd),
}),
],
}),
],
}),
});

View File

@@ -1,65 +0,0 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import Header from "./widgets/Header.js";
import PopupWindow from "../misc/PopupWindow.js";
import { Volume, Microhone, SinkSelector, AppMixer } from "./widgets/Volume.js";
import { NetworkToggle, WifiSelection } from "./widgets/Network.js";
import { BluetoothToggle, BluetoothDevices } from "./widgets/Bluetooth.js";
import { ThemeToggle, ThemeSelector } from "./widgets/Theme.js";
import { ProfileToggle, ProfileSelector } from "./widgets/AsusProfile.js";
import Media from "./widgets/Media.js";
import Brightness from "./widgets/Brightness.js";
import DND from "./widgets/DND.js";
import MicMute from "./widgets/MicMute.js";
import options from "../options.js";
const Row = (toggles = [], menus = []) =>
Widget.Box({
vertical: true,
children: [
Widget.Box({
class_name: "row horizontal",
children: toggles,
}),
...menus,
],
});
const Homogeneous = (toggles) =>
Widget.Box({
homogeneous: true,
children: toggles,
});
export default () =>
PopupWindow({
name: "quicksettings",
setup: (self) =>
self.hook(options.bar.position, () => {
self.anchor = ["right", options.bar.position.value];
if (options.bar.position.value === "top")
self.transition = "slide_down";
if (options.bar.position.value === "bottom")
self.transition = "slide_up";
}),
child: Widget.Box({
vertical: true,
children: [
Header(),
Widget.Box({
class_name: "sliders-box vertical",
vertical: true,
children: [
Row([Volume()], [SinkSelector(), AppMixer()]),
Microhone(),
Brightness(),
],
}),
Row(
[Homogeneous([ThemeToggle(), BluetoothToggle()]), MicMute()],
[ThemeSelector(), BluetoothDevices()],
),
Media(),
],
}),
});

View File

@@ -1,134 +0,0 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import App from "resource:///com/github/Aylur/ags/app.js";
import Variable from "resource:///com/github/Aylur/ags/variable.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import icons from "../icons.js";
/** name of the currently opened menu */
export const opened = Variable("");
App.connect("window-toggled", (_, name, visible) => {
if (name === "quicksettings" && !visible)
Utils.timeout(500, () => (opened.value = ""));
});
/**
* @param {string} name - menu name
* @param {(() => void) | false=} activate
*/
export const Arrow = (name, activate) => {
let deg = 0;
let iconOpened = false;
const icon = Widget.Icon(icons.ui.arrow.right).hook(opened, () => {
if (
(opened.value === name && !iconOpened) ||
(opened.value !== name && iconOpened)
) {
const step = opened.value === name ? 10 : -10;
iconOpened = !iconOpened;
for (let i = 0; i < 9; ++i) {
Utils.timeout(15 * i, () => {
deg += step;
icon.setCss(`-gtk-icon-transform: rotate(${deg}deg);`);
});
}
}
});
return Widget.Button({
child: icon,
on_clicked: () => {
opened.value = opened.value === name ? "" : name;
if (typeof activate === "function") activate();
},
});
};
/**
* @param {Object} o
* @param {string} o.name - menu name
* @param {import('gi://Gtk').Gtk.Widget} o.icon
* @param {import('gi://Gtk').Gtk.Widget} o.label
* @param {() => void} o.activate
* @param {() => void} o.deactivate
* @param {boolean=} o.activateOnArrow
* @param {[import('gi://GObject').GObject.Object, () => boolean]} o.connection
*/
export const ArrowToggleButton = ({
name,
icon,
label,
activate,
deactivate,
activateOnArrow = true,
connection: [service, condition],
}) =>
Widget.Box({
class_name: "toggle-button",
setup: (self) =>
self.hook(service, () => {
self.toggleClassName("active", condition());
}),
children: [
Widget.Button({
child: Widget.Box({
hexpand: true,
class_name: "label-box horizontal",
children: [icon, label],
}),
on_clicked: () => {
if (condition()) {
deactivate();
if (opened.value === name) opened.value = "";
} else {
activate();
}
},
}),
Arrow(name, activateOnArrow && activate),
],
});
/**
* @param {Object} o
* @param {string} o.name - menu name
* @param {import('gi://Gtk').Gtk.Widget} o.icon
* @param {import('gi://Gtk').Gtk.Widget} o.title
* @param {import('gi://Gtk').Gtk.Widget[]} o.content
*/
export const Menu = ({ name, icon, title, content }) =>
Widget.Revealer({
transition: "slide_down",
reveal_child: opened.bind().transform((v) => v === name),
child: Widget.Box({
class_names: ["menu", name],
vertical: true,
children: [
Widget.Box({
class_name: "title horizontal",
children: [icon, title],
}),
Widget.Separator(),
...content,
],
}),
});
/**
* @param {Object} o
* @param {import('gi://Gtk').Gtk.Widget} o.icon
* @param {() => void} o.toggle
* @param {[import('gi://GObject').GObject.Object, () => boolean]} o.connection
*/
export const SimpleToggleButton = ({
icon,
toggle,
connection: [service, condition],
}) =>
Widget.Button({
class_name: "simple-toggle",
setup: (self) =>
self.hook(service, () => {
self.toggleClassName("active", condition());
}),
child: icon,
on_clicked: toggle,
});

View File

@@ -1,61 +0,0 @@
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import icons from "../../icons.js";
import Asusctl from "../../services/asusctl.js";
import { ArrowToggleButton, Menu } from "../ToggleButton.js";
export const ProfileToggle = () =>
ArrowToggleButton({
name: "asusctl-profile",
icon: Widget.Icon({
icon: Asusctl.bind("profile").transform((p) => icons.asusctl.profile[p]),
}),
label: Widget.Label({
label: Asusctl.bind("profile"),
}),
connection: [Asusctl, () => Asusctl.profile !== "Balanced"],
activate: () => Asusctl.setProfile("Quiet"),
deactivate: () => Asusctl.setProfile("Balanced"),
activateOnArrow: false,
});
export const ProfileSelector = () =>
Menu({
name: "asusctl-profile",
icon: Widget.Icon({
icon: Asusctl.bind("profile").transform((p) => icons.asusctl.profile[p]),
}),
title: Widget.Label("Profile Selector"),
content: [
Widget.Box({
vertical: true,
hexpand: true,
children: [
Widget.Box({
vertical: true,
children: Asusctl.profiles.map((prof) =>
Widget.Button({
on_clicked: () => Asusctl.setProfile(prof),
child: Widget.Box({
children: [
Widget.Icon(icons.asusctl.profile[prof]),
Widget.Label(prof),
],
}),
}),
),
}),
],
}),
Widget.Separator(),
Widget.Button({
on_clicked: () => Utils.execAsync("rog-control-center"),
child: Widget.Box({
children: [
Widget.Icon(icons.ui.settings),
Widget.Label("Rog Control Center"),
],
}),
}),
],
});

View File

@@ -1,74 +0,0 @@
import Bluetooth from "resource:///com/github/Aylur/ags/service/bluetooth.js";
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import icons from "../../icons.js";
import { Menu, ArrowToggleButton } from "../ToggleButton.js";
export const BluetoothToggle = () =>
ArrowToggleButton({
name: "bluetooth",
icon: Widget.Icon({
icon: Bluetooth.bind("enabled").transform(
(p) => icons.bluetooth[p ? "enabled" : "disabled"],
),
}),
label: Widget.Label({
truncate: "end",
setup: (self) =>
self.hook(Bluetooth, () => {
if (!Bluetooth.enabled) return (self.label = "Disabled");
if (Bluetooth.connected_devices.length === 0)
return (self.label = "Not Connected");
if (Bluetooth.connected_devices.length === 1)
return (self.label = Bluetooth.connected_devices[0].alias);
self.label = `${Bluetooth.connected_devices.length} Connected`;
}),
}),
connection: [Bluetooth, () => Bluetooth.enabled],
deactivate: () => (Bluetooth.enabled = false),
activate: () => (Bluetooth.enabled = true),
});
/** @param {import('types/service/bluetooth').BluetoothDevice} device */
const DeviceItem = (device) =>
Widget.Box({
children: [
Widget.Icon(device.icon_name + "-symbolic"),
Widget.Label(device.name),
Widget.Label({
label: `${device.battery_percentage}%`,
visible: device.bind("battery_percentage").transform((p) => p > 0),
}),
Widget.Box({ hexpand: true }),
Widget.Spinner({
active: device.bind("connecting"),
visible: device.bind("connecting"),
}),
Widget.Switch({
active: device.connected,
visible: device.bind("connecting").transform((p) => !p),
setup: (self) =>
self.on("notify::active", () => {
device.setConnection(self.active);
}),
}),
],
});
export const BluetoothDevices = () =>
Menu({
name: "bluetooth",
icon: Widget.Icon(icons.bluetooth.disabled),
title: Widget.Label("Bluetooth"),
content: [
Widget.Box({
hexpand: true,
vertical: true,
children: Bluetooth.bind("devices").transform((ds) =>
ds.filter((d) => d.name).map(DeviceItem),
),
}),
],
});

View File

@@ -1,24 +0,0 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import icons from "../../icons.js";
import Brightness from "../../services/brightness.js";
const BrightnessSlider = () =>
Widget.Slider({
draw_value: false,
hexpand: true,
value: Brightness.bind("screen"),
on_change: ({ value }) => (Brightness.screen = value),
});
export default () =>
Widget.Box({
children: [
Widget.Button({
child: Widget.Icon(icons.brightness.indicator),
tooltip_text: Brightness.bind("screen").transform(
(v) => `Screen Brightness: ${Math.floor(v * 100)}%`,
),
}),
BrightnessSlider(),
],
});

View File

@@ -1,15 +0,0 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import Notifications from "resource:///com/github/Aylur/ags/service/notifications.js";
import icons from "../../icons.js";
import { SimpleToggleButton } from "../ToggleButton.js";
export default () =>
SimpleToggleButton({
icon: Widget.Icon({
icon: Notifications.bind("dnd").transform(
(dnd) => icons.notifications[dnd ? "silent" : "noisy"],
),
}),
toggle: () => (Notifications.dnd = !Notifications.dnd),
connection: [Notifications, () => Notifications.dnd],
});

View File

@@ -1,50 +0,0 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import Battery from "resource:///com/github/Aylur/ags/service/battery.js";
import PowerMenu from "../../services/powermenu.js";
import Lockscreen from "../../services/lockscreen.js";
import Avatar from "../../misc/Avatar.js";
import icons from "../../icons.js";
import { openSettings } from "../../settings/theme.js";
import { uptime } from "../../variables.js";
import DND from "./DND.js";
export default () =>
Widget.Box({
class_name: "header horizontal",
children: [
Avatar(),
Widget.Box({
hpack: "end",
vpack: "center",
hexpand: true,
children: [
/*Widget.Box({
class_name: "battery horizontal",
children: [
Widget.Icon({ icon: Battery.bind("icon_name") }),
Widget.Label({
label: Battery.bind("percent").transform((p) => `${p}%`),
}),
],
}),*/
DND(),
Widget.Label({
class_name: "uptime",
label: uptime.bind().transform((v) => `up: ${v}`),
}),
Widget.Button({
on_clicked: openSettings,
child: Widget.Icon(icons.ui.settings),
}),
Widget.Button({
on_clicked: () => Lockscreen.lockscreen(),
child: Widget.Icon(icons.lock),
}),
Widget.Button({
on_clicked: () => PowerMenu.action("shutdown"),
child: Widget.Icon(icons.powermenu.shutdown),
}),
],
}),
],
});

View File

@@ -1,91 +0,0 @@
import Mpris from "resource:///com/github/Aylur/ags/service/mpris.js";
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import * as mpris from "../../misc/mpris.js";
import options from "../../options.js";
/** @param {import('types/service/mpris').MprisPlayer} player */
const Footer = (player) =>
Widget.CenterBox({
class_name: "footer-box",
start_widget: Widget.Box({
class_name: "position",
children: [
mpris.PositionLabel(player),
mpris.Slash(player),
mpris.LengthLabel(player),
],
}),
center_widget: Widget.Box({
class_name: "controls",
children: [
mpris.ShuffleButton(player),
mpris.PreviousButton(player),
mpris.PlayPauseButton(player),
mpris.NextButton(player),
mpris.LoopButton(player),
],
}),
end_widget: mpris.PlayerIcon(player, {
symbolic: false,
hexpand: true,
hpack: "end",
}),
});
/** @param {import('types/service/mpris').MprisPlayer} player */
const TextBox = (player) =>
Widget.Box({
children: [
mpris.CoverArt(player, {
hpack: "end",
hexpand: false,
}),
Widget.Box({
hexpand: true,
vertical: true,
class_name: "labels",
children: [
mpris.TitleLabel(player, {
xalign: 0,
justification: "left",
wrap: true,
}),
mpris.ArtistLabel(player, {
xalign: 0,
justification: "left",
wrap: true,
}),
],
}),
],
});
/** @param {import('types/service/mpris').MprisPlayer} player */
const PlayerBox = (player) =>
Widget.Box({
class_name: `player ${player.name}`,
child: mpris.BlurredCoverArt(player, {
hexpand: true,
child: Widget.Box({
hexpand: true,
vertical: true,
children: [
TextBox(player),
mpris.PositionSlider(player),
Footer(player),
],
}),
}),
});
export default () =>
Widget.Box({
vertical: true,
class_name: "media vertical",
visible: Mpris.bind("players").transform((p) => p.length > 0),
children: Mpris.bind("players").transform((ps) =>
ps
.filter((p) => !options.mpris.black_list.value.includes(p.identity))
.map(PlayerBox),
),
});

View File

@@ -1,19 +0,0 @@
import Audio from "resource:///com/github/Aylur/ags/service/audio.js";
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import icons from "../../icons.js";
import { SimpleToggleButton } from "../ToggleButton.js";
export default () =>
SimpleToggleButton({
icon: Widget.Icon().hook(
Audio,
(self) => {
self.icon = Audio.microphone?.is_muted
? icons.audio.mic.muted
: icons.audio.mic.high;
},
"microphone-changed",
),
toggle: () => (Audio.microphone.is_muted = !Audio.microphone.is_muted),
connection: [Audio, () => Audio.microphone?.is_muted || false],
});

View File

@@ -1,71 +0,0 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import Network from "resource:///com/github/Aylur/ags/service/network.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import icons from "../../icons.js";
import { Menu, ArrowToggleButton } from "../ToggleButton.js";
import Applications from "resource:///com/github/Aylur/ags/service/applications.js";
export const NetworkToggle = () =>
ArrowToggleButton({
name: "network",
icon: Widget.Icon({
icon: Network.wifi.bind("icon_name"),
}),
label: Widget.Label({
truncate: "end",
label: Network.wifi
.bind("ssid")
.transform((ssid) => ssid || "Not Connected"),
}),
connection: [Network, () => Network.wifi.enabled],
deactivate: () => (Network.wifi.enabled = false),
activate: () => {
Network.wifi.enabled = true;
Network.wifi.scan();
},
});
export const WifiSelection = () =>
Menu({
name: "network",
icon: Widget.Icon({
icon: Network.wifi.bind("icon_name"),
}),
title: Widget.Label("Wifi Selection"),
content: [
Widget.Box({
vertical: true,
setup: (self) =>
self.hook(
Network,
() =>
(self.children = Network.wifi?.access_points.map((ap) =>
Widget.Button({
on_clicked: () =>
Utils.execAsync(`nmcli device wifi connect ${ap.bssid}`),
child: Widget.Box({
children: [
Widget.Icon(ap.iconName),
Widget.Label(ap.ssid || ""),
ap.active &&
Widget.Icon({
icon: icons.ui.tick,
hexpand: true,
hpack: "end",
}),
],
}),
}),
)),
),
}),
Widget.Separator(),
Widget.Button({
on_clicked: () =>
Applications.query("gnome-control-center")?.[0].launch(),
child: Widget.Box({
children: [Widget.Icon(icons.ui.settings), Widget.Label("Network")],
}),
}),
],
});

View File

@@ -1,55 +0,0 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import { ArrowToggleButton, Menu, opened } from "../ToggleButton.js";
import themes from "../../themes.js";
import icons from "../../icons.js";
import options from "../../options.js";
import { setTheme, openSettings } from "../../settings/theme.js";
export const ThemeToggle = () =>
ArrowToggleButton({
name: "theme",
icon: Widget.Label().bind("label", options.theme.icon),
label: Widget.Label().bind("label", options.theme.name),
connection: [opened, () => opened.value === "theme"],
activate: () => opened.setValue("theme"),
activateOnArrow: false,
deactivate: () => {},
});
export const ThemeSelector = () =>
Menu({
name: "theme",
icon: Widget.Label().bind("label", options.theme.icon),
title: Widget.Label("Theme Selector"),
content: [
...themes.map(({ name, icon }) =>
Widget.Button({
on_clicked: () => setTheme(name),
child: Widget.Box({
children: [
Widget.Label(icon),
Widget.Label(name),
Widget.Icon({
icon: icons.ui.tick,
hexpand: true,
hpack: "end",
visible: options.theme.name
.bind("value")
.transform((v) => v === name),
}),
],
}),
}),
),
Widget.Separator(),
Widget.Button({
on_clicked: openSettings,
child: Widget.Box({
children: [
Widget.Icon(icons.ui.settings),
Widget.Label("Theme Settings"),
],
}),
}),
],
});

View File

@@ -1,158 +0,0 @@
import Audio from "resource:///com/github/Aylur/ags/service/audio.js";
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import icons from "../../icons.js";
import FontIcon from "../../misc/FontIcon.js";
import { getAudioTypeIcon } from "../../utils.js";
import { Arrow } from "../ToggleButton.js";
import { Menu } from "../ToggleButton.js";
/** @param {'speaker' | 'microphone'=} type */
const VolumeIndicator = (type = "speaker") =>
Widget.Button({
on_clicked: () => (Audio[type].is_muted = !Audio[type].is_muted),
child: Widget.Icon().hook(Audio[type], (icon) => {
icon.icon =
type === "speaker"
? getAudioTypeIcon(Audio[type].icon_name || "")
: icons.audio.mic.high;
icon.tooltip_text = `Volume ${Math.floor(Audio[type].volume * 100)}%`;
}),
});
/** @param {'speaker' | 'microphone'=} type */
const VolumeSlider = (type = "speaker") =>
Widget.Slider({
hexpand: true,
draw_value: false,
on_change: ({ value }) => (Audio[type].volume = value),
setup: (self) =>
self.hook(Audio[type], () => {
self.value = Audio[type].volume || 0;
}),
});
export const Volume = () =>
Widget.Box({
children: [
VolumeIndicator("speaker"),
VolumeSlider("speaker"),
Widget.Box({
vpack: "center",
child: Arrow("sink-selector"),
}),
Widget.Box({
vpack: "center",
child: Arrow("app-mixer"),
visible: Audio.bind("apps").transform((a) => a.length > 0),
}),
],
});
export const Microhone = () =>
Widget.Box({
class_name: "slider horizontal",
visible: Audio.bind("recorders").transform((a) => a.length > 0),
children: [VolumeIndicator("microphone"), VolumeSlider("microphone")],
});
/** @param {import('types/service/audio').Stream} stream */
const MixerItem = (stream) =>
Widget.Box({
hexpand: true,
class_name: "mixer-item horizontal",
children: [
Widget.Icon({
tooltip_text: stream.bind("name").transform((n) => n || ""),
icon: stream.bind("name").transform((n) => {
return Utils.lookUpIcon(n || "") ? n || "" : icons.mpris.fallback;
}),
}),
Widget.Box({
vertical: true,
children: [
Widget.Label({
xalign: 0,
truncate: "end",
label: stream.bind("description").transform((d) => d || ""),
}),
Widget.Slider({
hexpand: true,
draw_value: false,
value: stream.bind("volume"),
on_change: ({ value }) => (stream.volume = value),
}),
],
}),
Widget.Label({
xalign: 1,
label: stream.bind("volume").transform((v) => `${Math.floor(v * 100)}`),
}),
],
});
/** @param {import('types/service/audio').Stream} stream */
const SinkItem = (stream) =>
Widget.Button({
hexpand: true,
on_clicked: () => (Audio.speaker = stream),
child: Widget.Box({
children: [
Widget.Icon({
icon: getAudioTypeIcon(stream.icon_name || ""),
tooltip_text: stream.icon_name,
}),
Widget.Label(
(stream.description || "").split(" ").slice(0, 4).join(" "),
),
Widget.Icon({
icon: icons.ui.tick,
hexpand: true,
hpack: "end",
visible: Audio.speaker
.bind("stream")
.transform((s) => s === stream.stream),
}),
],
}),
});
const SettingsButton = () =>
Widget.Button({
on_clicked: () => Utils.execAsync("pavucontrol"),
hexpand: true,
child: Widget.Box({
children: [Widget.Icon(icons.ui.settings), Widget.Label("Settings")],
}),
});
export const AppMixer = () =>
Menu({
name: "app-mixer",
icon: FontIcon(icons.audio.mixer),
title: Widget.Label("App Mixer"),
content: [
Widget.Box({
vertical: true,
children: Audio.bind("apps").transform((a) => a.map(MixerItem)),
}),
Widget.Separator(),
SettingsButton(),
],
});
export const SinkSelector = () =>
Menu({
name: "sink-selector",
icon: Widget.Icon(icons.audio.type.headset),
title: Widget.Label("Sink Selector"),
content: [
Widget.Box({
vertical: true,
children: Audio.bind("speakers").transform((a) => a.map(SinkItem)),
}),
Widget.Separator(),
SettingsButton(),
],
});

View File

@@ -1,75 +0,0 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import Gtk from "gi://Gtk";
import options from "../options.js";
/** @param {'topleft' | 'topright' | 'bottomleft' | 'bottomright'} place */
const Corner = (place) =>
Widget.DrawingArea({
class_name: "corner",
hexpand: true,
vexpand: true,
hpack: place.includes("left") ? "start" : "end",
vpack: place.includes("top") ? "start" : "end",
setup: (self) =>
self
.hook(options.radii, () => {
const r = options.radii.value * 2;
self.set_size_request(r, r);
})
.connect("draw", (self, cr) => {
const context = self.get_style_context();
const c = context.get_property(
"background-color",
Gtk.StateFlags.NORMAL,
);
const r = context.get_property(
"border-radius",
Gtk.StateFlags.NORMAL,
);
switch (place) {
case "topleft":
cr.arc(r, r, r, Math.PI, (3 * Math.PI) / 2);
cr.lineTo(0, 0);
break;
case "topright":
cr.arc(0, r, r, (3 * Math.PI) / 2, 2 * Math.PI);
cr.lineTo(r, 0);
break;
case "bottomleft":
cr.arc(r, 0, r, Math.PI / 2, Math.PI);
cr.lineTo(0, r);
break;
case "bottomright":
cr.arc(0, 0, r, 0, Math.PI / 2);
cr.lineTo(r, r);
break;
}
cr.closePath();
cr.setSourceRGBA(c.red, c.green, c.blue, c.alpha);
cr.fill();
}),
});
/** @type {Array<'topleft' | 'topright' | 'bottomleft' | 'bottomright'>} */
const places = ["topleft", "topright", "bottomleft", "bottomright"];
/** @param {number} monitor */
export default (monitor) =>
places.map((place) =>
Widget.Window({
name: `corner${monitor}${place}`,
monitor,
class_name: "corner",
anchor: [
place.includes("top") ? "top" : "bottom",
place.includes("right") ? "right" : "left",
],
visible: options.desktop.screen_corners.bind("value"),
child: Corner(place),
}),
);

View File

@@ -1,70 +0,0 @@
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import Service from "resource:///com/github/Aylur/ags/service.js";
class Asusctl extends Service {
static {
Service.register(
this,
{},
{
profile: ["string", "r"],
mode: ["string", "r"],
},
);
}
profiles = /** @type {const} */ (["Performance", "Balanced", "Quiet"]);
#profile = "Balanced";
#mode = "Hyprid";
nextProfile() {
Utils.execAsync("asusctl profile -n")
.then(() => {
this.#profile = Utils.exec("asusctl profile -p").split(" ")[3];
this.changed("profile");
})
.catch(console.error);
}
/** @param {'Performance' | 'Balanced' | 'Quiet'} prof */
setProfile(prof) {
Utils.execAsync(`asusctl profile --profile-set ${prof}`)
.then(() => {
this.#profile = prof;
this.changed("profile");
})
.catch(console.error);
}
nextMode() {
Utils.execAsync(
`supergfxctl -m ${this.#mode === "Hybrid" ? "Integrated" : "Hybrid"}`,
)
.then(() => {
this.#mode = Utils.exec("supergfxctl -g");
this.changed("profile");
})
.catch(console.error);
}
constructor() {
super();
if (Utils.exec("which asusctl")) {
this.available = true;
this.#profile = Utils.exec("asusctl profile -p").split(" ")[3];
Utils.execAsync("supergfxctl -g").then((mode) => (this.#mode = mode));
} else {
this.available = false;
}
}
get profile() {
return this.#profile;
}
get mode() {
return this.#mode;
}
}
export default new Asusctl();

View File

@@ -1,77 +0,0 @@
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import Service from "resource:///com/github/Aylur/ags/service.js";
import options from "../options.js";
import { dependencies } from "../utils.js";
const KBD = options.brightnessctlKBD;
class Brightness extends Service {
static {
Service.register(
this,
{},
{
screen: ["float", "rw"],
kbd: ["int", "rw"],
},
);
}
#kbd = 0;
#kbdMax = 3;
#screen = 0;
get kbd() {
return this.#kbd;
}
get screen() {
return this.#screen;
}
set kbd(value) {
if (!dependencies(["brightnessctl"])) return;
if (value < 0 || value > this.#kbdMax) return;
Utils.execAsync(`brightnessctl -d ${KBD} s ${value} -q`)
.then(() => {
this.#kbd = value;
this.changed("kbd");
})
.catch(console.error);
}
set screen(percent) {
if (!dependencies(["gbmonctl"])) return;
if (percent < 0) percent = 0;
if (percent > 1) percent = 1;
Utils.execAsync(
`gbmonctl --prop brightness -val ${Math.min(
Math.max(Math.floor(percent * 100), 0),
100,
)}`,
)
.then(() => {
this.#screen = percent;
this.changed("screen");
})
.catch(console.error);
}
constructor() {
super();
if (dependencies(["brightnessctl"])) {
this.#kbd = Number(Utils.exec(`brightnessctl -d ${KBD} g`));
this.#kbdMax = Number(Utils.exec(`brightnessctl -d ${KBD} m`));
this.#screen =
Number(Utils.exec("brightnessctl g")) /
Number(Utils.exec("brightnessctl m"));
}
}
}
export default new Brightness();

View File

@@ -1,73 +0,0 @@
import { Variable } from "resource:///com/github/Aylur/ags/variable.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import Service from "resource:///com/github/Aylur/ags/service.js";
import { dependencies } from "../utils.js";
import icons from "../icons.js";
const COLORS_CACHE = Utils.CACHE_DIR + "/colorpicker.json";
class Colors extends Service {
static {
Service.register(
this,
{},
{
colors: ["jsobject"],
},
);
}
/** @type {Variable<string[]>} */
#colors = new Variable([]);
get colors() {
return this.#colors.value;
}
#notifID = 0;
constructor() {
super();
this.#colors.connect("changed", () => this.changed("colors"));
Utils.readFileAsync(COLORS_CACHE)
.then((out) => this.#colors.setValue(JSON.parse(out || "[]")))
.catch(() => print("no colorpicker cache found"));
}
/** @param {string} color */
wlCopy(color) {
Utils.execAsync(["wl-copy", color]).catch((err) => console.error(err));
}
async pick() {
if (!dependencies(["hyprpicker"])) return;
const color = await Utils.execAsync("hyprpicker");
if (!color) return;
this.wlCopy(color);
const list = this.#colors.value;
if (!list.includes(color)) {
list.push(color);
if (list.length > 10) list.shift();
this.#colors.value = list;
Utils.writeFile(JSON.stringify(list, null, 2), COLORS_CACHE).catch(
(err) => console.error(err),
);
}
const n = await Utils.notify({
id: this.#notifID,
iconName: icons.ui.colorpicker,
summary: color,
actions: {
Copy: () => this.wlCopy(color),
},
});
this.#notifID = n.id;
}
}
export default new Colors();

View File

@@ -1,30 +0,0 @@
import Service from "resource:///com/github/Aylur/ags/service.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import App from "resource:///com/github/Aylur/ags/app.js";
const authpy = App.configDir + "/js/lockscreen/auth.py";
class Lockscreen extends Service {
static {
Service.register(this, {
lock: ["boolean"],
authenticating: ["boolean"],
});
}
lockscreen() {
this.emit("lock", true);
}
/** @param {string} password */
auth(password) {
this.emit("authenticating", true);
Utils.execAsync([authpy, password])
.then((out) => {
this.emit("lock", out !== "True");
this.emit("authenticating", false);
})
.catch((err) => console.error(err));
}
}
export default new Lockscreen();

View File

@@ -1,58 +0,0 @@
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import Service from "resource:///com/github/Aylur/ags/service.js";
import Audio from "resource:///com/github/Aylur/ags/service/audio.js";
import icons from "../icons.js";
import { getAudioTypeIcon } from "../utils.js";
import Brightness from "./brightness.js";
class Indicator extends Service {
static {
Service.register(this, {
popup: ["double", "string"],
});
}
#delay = 1500;
#count = 0;
/**
* @param {number} value - 0 < v < 1
* @param {string} icon
*/
popup(value, icon) {
this.emit("popup", value, icon);
this.#count++;
Utils.timeout(this.#delay, () => {
this.#count--;
if (this.#count === 0) this.emit("popup", -1, icon);
});
}
speaker() {
this.popup(
Audio.speaker?.volume || 0,
getAudioTypeIcon(Audio.speaker?.icon_name || ""),
);
}
display() {
// brightness is async, so lets wait a bit
Utils.timeout(10, () =>
this.popup(Brightness.screen, icons.brightness.screen),
);
}
kbd() {
// brightness is async, so lets wait a bit
Utils.timeout(10, () =>
this.popup((Brightness.kbd * 33 + 1) / 100, icons.brightness.keyboard),
);
}
connect(event = "popup", callback) {
return super.connect(event, callback);
}
}
export default new Indicator();

View File

@@ -1,43 +0,0 @@
import App from "resource:///com/github/Aylur/ags/app.js";
import Service from "resource:///com/github/Aylur/ags/service.js";
class PowerMenu extends Service {
static {
Service.register(
this,
{},
{
title: ["string"],
cmd: ["string"],
},
);
}
#title = "";
#cmd = "";
get title() {
return this.#title;
}
get cmd() {
return this.#cmd;
}
/** @param {'sleep' | 'reboot' | 'logout' | 'shutdown'} action */
action(action) {
[this.#cmd, this.#title] = {
sleep: ["systemctl suspend", "Sleep"],
reboot: ["systemctl reboot", "Reboot"],
logout: ["pkill Hyprland", "Log Out"],
shutdown: ["shutdown now", "Shutdown"],
}[action];
this.notify("cmd");
this.notify("title");
this.emit("changed");
App.closeWindow("powermenu");
App.openWindow("verification");
}
}
export default new PowerMenu();

View File

@@ -1,112 +0,0 @@
import Service from "resource:///com/github/Aylur/ags/service.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import App from "resource:///com/github/Aylur/ags/app.js";
import GLib from "gi://GLib";
import { dependencies } from "../utils.js";
const now = () => GLib.DateTime.new_now_local().format("%Y-%m-%d_%H-%M-%S");
class Recorder extends Service {
static {
Service.register(
this,
{},
{
timer: ["int"],
recording: ["boolean"],
},
);
}
#path = GLib.get_home_dir() + "/Videos/Screencasting";
#file = "";
#interval = 0;
recording = false;
timer = 0;
async start() {
if (!dependencies(["slurp", "wf-recorder"])) return;
if (this.recording) return;
const area = await Utils.execAsync("slurp");
Utils.ensureDirectory(this.#path);
this.#file = `${this.#path}/${now()}.mp4`;
Utils.execAsync(["wf-recorder", "-g", area, "-f", this.#file]);
this.recording = true;
this.changed("recording");
this.timer = 0;
this.#interval = Utils.interval(1000, () => {
this.changed("timer");
this.timer++;
});
}
async stop() {
if (!dependencies(["notify-send"])) return;
if (!this.recording) return;
Utils.execAsync("killall -INT wf-recorder");
this.recording = false;
this.changed("recording");
GLib.source_remove(this.#interval);
const res = await Utils.execAsync([
"notify-send",
"-A",
"files=Show in Files",
"-A",
"view=View",
"-i",
"video-x-generic-symbolic",
"Screenrecord",
this.#file,
]);
if (res === "files") Utils.execAsync("xdg-open " + this.#path);
if (res === "view") Utils.execAsync("xdg-open " + this.#file);
}
async screenshot(full = false) {
if (!dependencies(["slurp", "wayshot"])) return;
const path = GLib.get_home_dir() + "/Pictures/Screenshots";
const file = `${path}/${now()}.png`;
Utils.ensureDirectory(path);
await Utils.execAsync(
["wayshot", "-f", file].concat(
full ? [] : ["-s", await Utils.execAsync("slurp")],
),
);
Utils.execAsync(["bash", "-c", `wl-copy < ${file}`]);
const res = await Utils.execAsync([
"notify-send",
"-A",
"files=Show in Files",
"-A",
"view=View",
"-A",
"edit=Edit",
"-i",
file,
"Screenshot",
file,
]);
if (res === "files") Utils.execAsync("xdg-open " + path);
if (res === "view") Utils.execAsync("xdg-open " + file);
if (res === "edit") Utils.execAsync(["swappy", "-f", file]);
App.closeWindow("dashboard");
}
}
export default new Recorder();

View File

@@ -1,297 +0,0 @@
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import App from "resource:///com/github/Aylur/ags/app.js";
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import RegularWindow from "../misc/RegularWindow.js";
import Variable from "resource:///com/github/Aylur/ags/variable.js";
import icons from "../icons.js";
import { getOptions, getValues } from "./option.js";
import options from "../options.js";
const optionsList = getOptions();
const categories = Array.from(
new Set(optionsList.map((opt) => opt.category)),
).filter((category) => category !== "exclude");
const currentPage = Variable(categories[0]);
const search = Variable("");
const showSearch = Variable(false);
showSearch.connect("changed", ({ value }) => {
if (!value) search.value = "";
});
/** @param {import('./option.js').Opt<string>} opt */
const EnumSetter = (opt) => {
const lbl = Widget.Label().bind("label", opt);
const step = (dir = 1) => {
const i = opt.enums.findIndex((i) => i === lbl.label);
opt.setValue(
dir > 0
? i + dir > opt.enums.length - 1
? opt.enums[0]
: opt.enums[i + dir]
: i + dir < 0
? opt.enums[opt.enums.length - 1]
: opt.enums[i + dir],
true,
);
};
const next = Widget.Button({
child: Widget.Icon(icons.ui.arrow.right),
on_clicked: () => step(+1),
});
const prev = Widget.Button({
child: Widget.Icon(icons.ui.arrow.left),
on_clicked: () => step(-1),
});
return Widget.Box({
class_name: "enum-setter",
children: [prev, lbl, next],
});
};
/** @param {import('./option.js').Opt} opt */
const Setter = (opt) => {
switch (opt.type) {
case "number":
return Widget.SpinButton({
setup(self) {
self.set_range(0, 1000);
self.set_increments(1, 5);
self.on("value-changed", () => opt.setValue(self.value, true));
self.hook(opt, () => (self.value = opt.value));
},
});
case "float":
case "object":
return Widget.Entry({
on_accept: (self) => opt.setValue(JSON.parse(self.text || ""), true),
setup: (self) =>
self.hook(opt, () => (self.text = JSON.stringify(opt.value))),
});
case "string":
return Widget.Entry({
on_accept: (self) => opt.setValue(self.text, true),
setup: (self) => self.hook(opt, () => (self.text = opt.value)),
});
case "enum":
return EnumSetter(opt);
case "boolean":
return Widget.Switch()
.on("notify::active", (self) => opt.setValue(self.active, true))
.hook(opt, (self) => (self.active = opt.value));
case "img":
return Widget.FileChooserButton().on("selection-changed", (self) => {
opt.setValue(self.get_uri()?.replace("file://", ""), true);
});
case "font":
return Widget.FontButton({
show_size: false,
use_size: false,
setup: (self) =>
self
.on("notify::font", ({ font }) => opt.setValue(font, true))
.hook(opt, () => (self.font = opt.value)),
});
default:
return Widget.Label({
label: "no setter with type " + opt.type,
});
}
};
/** @param {import('./option.js').Opt} opt */
const Row = (opt) =>
Widget.Box({
class_name: "row",
attribute: opt,
children: [
Widget.Box({
vertical: true,
vpack: "center",
children: [
opt.title &&
Widget.Label({
xalign: 0,
class_name: "summary",
label: opt.title,
}),
Widget.Label({
xalign: 0,
class_name: "id",
label: `id: "${opt.id}"`,
}),
],
}),
Widget.Box({ hexpand: true }),
Widget.Box({
vpack: "center",
vertical: true,
children: [
Widget.Box({
hpack: "end",
child: Setter(opt),
}),
opt.note &&
Widget.Label({
xalign: 1,
class_name: "note",
label: opt.note,
}),
],
}),
],
});
/** @param {string} category */
const Page = (category) =>
Widget.Scrollable({
vexpand: true,
class_name: "page",
child: Widget.Box({
class_name: "page-content vertical",
vertical: true,
setup: (self) =>
self.hook(search, () => {
for (const child of self.children) {
child.visible =
child.attribute.id.includes(search.value) ||
child.attribute.title.includes(search.value) ||
child.attribute.note.includes(search.value);
}
}),
children: optionsList
.filter((opt) => opt.category.includes(category))
.map(Row),
}),
});
const sidebar = Widget.Revealer({
reveal_child: search.bind().transform((v) => !v),
transition: "slide_right",
child: Widget.Box({
hexpand: false,
vertical: true,
children: [
Widget.Box({
class_name: "sidebar-header",
children: [
Widget.Button({
hexpand: true,
label: icons.dialog.Search + " Search",
on_clicked: () => (showSearch.value = !showSearch.value),
}),
Widget.Button({
hpack: "end",
child: Widget.Icon(icons.ui.info),
on_clicked: () => App.toggleWindow("about"),
}),
],
}),
Widget.Scrollable({
vexpand: true,
hscroll: "never",
child: Widget.Box({
class_name: "sidebar-box vertical",
vertical: true,
children: [
...categories.map((name) =>
Widget.Button({
label: (icons.dialog[name] || "") + " " + name,
xalign: 0,
class_name: currentPage
.bind()
.transform((v) => `${v === name ? "active" : ""}`),
on_clicked: () => currentPage.setValue(name),
}),
),
],
}),
}),
Widget.Box({
class_name: "sidebar-footer",
child: Widget.Button({
class_name: "copy",
child: Widget.Label({
label: " Save",
xalign: 0,
}),
hexpand: true,
on_clicked: () => {
Utils.execAsync(["wl-copy", getValues()]);
Utils.execAsync([
"notify-send",
"-i",
"preferences-desktop-theme-symbolic",
"Theme copied to clipboard",
'To save it permanently, make a new theme in <span weight="bold">themes.js</span>',
]);
},
}),
}),
],
}),
});
const searchEntry = Widget.Revealer({
transition: "slide_down",
reveal_child: showSearch.bind(),
transition_duration: options.transition.bind("value"),
child: Widget.Entry({
setup: (self) =>
self.hook(showSearch, () => {
if (!showSearch.value) self.text = "";
if (showSearch.value) self.grab_focus();
}),
hexpand: true,
class_name: "search",
placeholder_text: "Search Options",
secondary_icon_name: icons.apps.search,
on_change: ({ text }) => (search.value = text || ""),
}),
});
const categoriesStack = Widget.Stack({
transition: "slide_left_right",
children: categories.reduce((obj, name) => {
obj[name] = Page(name);
return obj;
}, {}),
shown: currentPage.bind(),
visible: search.bind().transform((v) => !v),
});
const searchPage = Widget.Box({
visible: search.bind().transform((v) => !!v),
child: Page(""),
});
export default RegularWindow({
name: "settings-dialog",
title: "Settings",
setup: (win) =>
win
.on("delete-event", () => {
win.hide();
return true;
})
.on("key-press-event", (_, event) => {
if (event.get_keyval()[1] === imports.gi.Gdk.KEY_Escape) {
showSearch.setValue(false);
search.setValue("");
}
})
.set_default_size(800, 500),
child: Widget.Box({
children: [
sidebar,
Widget.Box({
vertical: true,
children: [searchEntry, categoriesStack, searchPage],
}),
],
}),
});

View File

@@ -1,40 +0,0 @@
import Mpris from "resource:///com/github/Aylur/ags/service/mpris.js";
export async function globals() {
try {
globalThis.options = (await import("../options.js")).default;
globalThis.iconBrowser = (await import("../misc/IconBrowser.js")).default;
globalThis.app = (
await import("resource:///com/github/Aylur/ags/app.js")
).default;
globalThis.audio = (
await import("resource:///com/github/Aylur/ags/service/audio.js")
).default;
globalThis.recorder = (await import("../services/screenrecord.js")).default;
globalThis.brightness = (await import("../services/brightness.js")).default;
globalThis.indicator = (
await import("../services/onScreenIndicator.js")
).default;
globalThis.app = (
await import("resource:///com/github/Aylur/ags/app.js")
).default;
Mpris.players.forEach((player) => {
player.connect("changed", (player) => {
globalThis.mpris = player || Mpris.players[0];
});
});
Mpris.connect("player-added", (mpris, bus) => {
mpris.getPlayer(bus)?.connect("changed", (player) => {
globalThis.mpris = player || Mpris.players[0];
});
});
Mpris.connect("player-closed", () => {
globalThis.mpris = Mpris.players[0];
});
} catch (error) {
logError(error);
}
}

View File

@@ -1,69 +0,0 @@
import App from "resource:///com/github/Aylur/ags/app.js";
import Hyprland from "resource:///com/github/Aylur/ags/service/hyprland.js";
import options from "../options.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
const noIgnorealpha = ["verification", "powermenu", "lockscreen"];
/** @param {Array<string>} batch */
function sendBatch(batch) {
const cmd = batch
.filter((x) => !!x)
.map((x) => `keyword ${x}`)
.join("; ");
Hyprland.sendMessage(`[[BATCH]]/${cmd}`);
}
/** @param {string} scss */
function getColor(scss) {
if (scss.includes("#")) return scss.replace("#", "");
if (scss.includes("$")) {
const opt = options
.list()
.find((opt) => opt.scss === scss.replace("$", ""));
return opt?.value.replace("#", "") || "ff0000";
}
}
export function hyprlandInit() {
sendBatch(
App.windows.flatMap(({ name }) => [
`layerrule blur, ${name}`,
noIgnorealpha.some((skip) => name?.includes(skip))
? ""
: `layerrule ignorealpha 0.3, ${name}`,
]),
);
}
export async function setupHyprland() {
/*Hyprland.event("activewindowv2", async (addr) => {
const client = Hyprland.getClient(addr);
if (!client.pinned || !client.floating) return;
const x = client.at[0];
console.log(
await Utils.execAsync(`hyprctl dispatch moveactive exact ${x} 80`),
);
});*/
const wm_gaps = Math.floor(
options.hypr.wm_gaps_multiplier.value * options.spacing.value,
);
const border_width = options.border.width.value;
const radii = options.radii.value;
const drop_shadow = options.desktop.drop_shadow.value;
const inactive_border = options.hypr.inactive_border.value;
const accent = getColor(options.theme.accent.accent.value);
sendBatch([
`general:border_size ${border_width}`,
`general:gaps_out ${wm_gaps}`,
`general:gaps_in ${Math.floor(wm_gaps / 2)}`,
`general:col.active_border rgba(${accent}ff)`,
`general:col.inactive_border ${inactive_border}`,
`decoration:rounding ${radii}`,
`decoration:drop_shadow ${drop_shadow ? "yes" : "no"}`,
]);
}

View File

@@ -1,198 +0,0 @@
import {
CACHE_DIR,
readFile,
writeFile,
} from "resource:///com/github/Aylur/ags/utils.js";
import { exec } from "resource:///com/github/Aylur/ags/utils.js";
import options from "../options.js";
import Service from "resource:///com/github/Aylur/ags/service.js";
import { reloadScss } from "./scss.js";
import { setupHyprland } from "./hyprland.js";
const CACHE_FILE = CACHE_DIR + "/options.json";
/** object that holds the overriedden values */
let cacheObj = JSON.parse(readFile(CACHE_FILE) || "{}");
/**
* @template T
* @typedef {Object} OptionConfig
* @property {string=} scss - name of scss variable set to "exclude" to not include it in the generated scss file
* @property {string=} unit - scss unit on numbers, default is "px"
* @property {string=} title
* @property {string=} note
* @property {string=} category
* @property {boolean=} noReload - don't reload css & hyprland on change
* @property {boolean=} persist - ignore reset call
* @property {'object' | 'string' | 'img' | 'number' | 'float' | 'font' | 'enum' =} type
* @property {Array<string> =} enums
* @property {(value: T) => any=} format
* @property {(value: T) => any=} scssFormat
*/
/** @template T */
export class Opt extends Service {
static {
Service.register(
this,
{},
{
value: ["jsobject"],
},
);
}
#value;
#scss = "";
unit = "px";
noReload = false;
persist = false;
id = "";
title = "";
note = "";
type = "";
category = "";
/** @type {Array<string>} */
enums = [];
/** @type {(v: T) => any} */
format = (v) => v;
/** @type {(v: T) => any} */
scssFormat = (v) => v;
/**
* @param {T} value
* @param {OptionConfig<T> =} config
*/
constructor(value, config) {
super();
this.#value = value;
this.defaultValue = value;
this.type = typeof value;
if (config) Object.keys(config).forEach((c) => (this[c] = config[c]));
import("../options.js").then(this.#init.bind(this));
}
set scss(scss) {
this.#scss = scss;
}
get scss() {
return this.#scss || this.id.split(".").join("-").split("_").join("-");
}
#init() {
getOptions(); // sets the ids as a side effect
if (cacheObj[this.id] !== undefined) this.setValue(cacheObj[this.id]);
const words = this.id
.split(".")
.flatMap((w) => w.split("_"))
.map((word) => word.charAt(0).toUpperCase() + word.slice(1));
this.title ||= words.join(" ");
this.category ||= words.length === 1 ? "General" : words.at(0) || "General";
this.connect("changed", () => {
cacheObj[this.id] = this.value;
writeFile(JSON.stringify(cacheObj, null, 2), CACHE_FILE);
});
}
get value() {
return this.#value;
}
set value(value) {
this.setValue(value);
}
/** @param {T} value */
setValue(value, reload = false) {
if (typeof value !== typeof this.defaultValue) {
console.error(
Error(
`WrongType: Option "${this.id}" can't be set to ${value}, ` +
`expected "${typeof this.defaultValue}", but got "${typeof value}"`,
),
);
return;
}
if (this.value !== value) {
this.#value = this.format(value);
this.changed("value");
if (reload && !this.noReload) {
reloadScss();
setupHyprland();
}
}
}
reset(reload = false) {
if (!this.persist) this.setValue(this.defaultValue, reload);
}
}
/**
* @template T
* @param {T} value
* @param {OptionConfig<T> =} config
* @returns {Opt<T>}
*/
export function Option(value, config) {
return new Opt(value, config);
}
/** @returns {Array<Opt<any>>} */
export function getOptions(object = options, path = "") {
return Object.keys(object).flatMap((key) => {
/** @type Option<any> */
const obj = object[key];
const id = path ? path + "." + key : key;
if (obj instanceof Opt) {
obj.id = id;
return obj;
}
if (typeof obj === "object") return getOptions(obj, id);
return [];
});
}
export function resetOptions() {
exec(`rm -rf ${CACHE_FILE}`);
cacheObj = {};
getOptions().forEach((opt) => opt.reset());
}
export function getValues() {
const obj = {};
for (const opt of getOptions()) {
if (opt.category !== "exclude") obj[opt.id] = opt.value;
}
return JSON.stringify(obj, null, 2);
}
/** @param {string | object} config */
export function apply(config) {
const options = getOptions();
const settings = typeof config === "string" ? JSON.parse(config) : config;
for (const id of Object.keys(settings)) {
const opt = options.find((opt) => opt.id === id);
if (!opt) {
print(`No option with id: "${id}"`);
continue;
}
opt.setValue(settings[id]);
}
}

View File

@@ -1,62 +0,0 @@
import App from "resource:///com/github/Aylur/ags/app.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import { getOptions } from "./option.js";
export function scssWatcher() {
return Utils.subprocess(
[
"inotifywait",
"--recursive",
"--event",
"create,modify",
"-m",
App.configDir + "/scss",
],
reloadScss,
() => print("missing dependancy for css hotreload: inotify-tools"),
);
}
/**
* generate an scss file that makes every option available as a variable
* based on the passed scss parameter or the path in the object
*
* e.g
* options.bar.style.value => $bar-style
*/
export async function reloadScss() {
const opts = getOptions();
const vars = opts.map((opt) => {
if (opt.scss === "exclude") return "";
const unit = typeof opt.value === "number" ? opt.unit : "";
const value = opt.scssFormat ? opt.scssFormat(opt.value) : opt.value;
return `$${opt.scss}: ${value}${unit};`;
});
const bar_style = opts.find((opt) => opt.id === "bar.style")?.value || "";
const additional =
bar_style === "normal"
? "//"
: `
window#quicksettings .window-content {
margin-right: $wm-gaps;
}
`;
try {
const tmp = "/tmp/ags/scss";
Utils.ensureDirectory(tmp);
await Utils.writeFile(vars.join("\n"), `${tmp}/options.scss`);
await Utils.writeFile(additional, `${tmp}/additional.scss`);
await Utils.execAsync(
`sassc ${App.configDir}/scss/main.scss ${tmp}/style.css`,
);
App.resetCss();
App.applyCss(`${tmp}/style.css`);
} catch (error) {
if (error instanceof Error) console.error(error.message);
if (typeof error === "string") console.error(error);
}
}

View File

@@ -1,126 +0,0 @@
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import Battery from "resource:///com/github/Aylur/ags/service/battery.js";
import Notifications from "resource:///com/github/Aylur/ags/service/notifications.js";
import options from "../options.js";
import icons from "../icons.js";
import { reloadScss } from "./scss.js";
import { wallpaper } from "./wallpaper.js";
import { hyprlandInit, setupHyprland } from "./hyprland.js";
import { globals } from "./globals.js";
import { showAbout } from "../about/about.js";
import Gtk from "gi://Gtk?version=3.0";
export function init() {
notificationBlacklist();
warnOnLowBattery();
globals();
tmux();
kitty();
gsettigsColorScheme();
gtkFontSettings();
dependandOptions();
reloadScss();
hyprlandInit();
setupHyprland();
wallpaper();
showAbout();
}
function dependandOptions() {
options.bar.style.connect("changed", ({ value }) => {
if (value !== "normal")
options.desktop.screen_corners.setValue(false, true);
});
}
function kitty() {
if (!Utils.exec("which kitty")) return;
console.log("kitty");
options.theme.scheme.connect("changed", ({ value }) =>
Utils.execAsync(
`kitty +kitten themes --reload-in=all --config-file-name /home/theaninova/.config/kitty/current-colors.conf Catppuccin-${
value === "light" ? "Latte" : "Frappe"
}`,
),
);
}
function tmux() {
if (!Utils.exec("which tmux")) return;
/** @param {string} scss */
function getColor(scss) {
if (scss.includes("#")) return scss;
if (scss.includes("$")) {
const opt = options
.list()
.find((opt) => opt.scss === scss.replace("$", ""));
return opt?.value;
}
}
options.theme.accent.accent.connect("changed", ({ value }) =>
Utils.execAsync(`tmux set @main_accent ${getColor(value)}`).catch((err) =>
console.error(err.message),
),
);
}
function gsettigsColorScheme() {
if (!Utils.exec("which gsettings")) return;
options.theme.scheme.connect("changed", ({ value }) => {
const gsettings = "gsettings set org.gnome.desktop.interface color-scheme";
Utils.execAsync(`${gsettings} "prefer-${value}"`).catch((err) =>
console.error(err.message),
);
});
}
function gtkFontSettings() {
const settings = Gtk.Settings.get_default();
if (!settings) {
console.error(Error("Gtk.Settings unavailable"));
return;
}
const callback = () => {
const { size, font } = options.font;
settings.gtk_font_name = `${font.value} ${size.value}`;
};
options.font.font.connect("notify::value", callback);
options.font.size.connect("notify::value", callback);
}
function notificationBlacklist() {
Notifications.connect("notified", (_, id) => {
const n = Notifications.getNotification(id);
options.notifications.black_list.value.forEach((item) => {
if (n?.app_name.includes(item) || n?.app_entry?.includes(item)) n.close();
});
});
}
function warnOnLowBattery() {
Battery.connect("notify::percent", () => {
const low = options.battery.low.value;
if (
Battery.percent !== low ||
Battery.percent !== low / 2 ||
!Battery.charging
)
return;
Utils.execAsync([
"notify-send",
`${Battery.percent}% Battery Percentage`,
"-i",
icons.battery.warning,
"-u",
"critical",
]);
});
}

View File

@@ -1,68 +0,0 @@
import App from "resource:///com/github/Aylur/ags/app.js";
import options from "../options.js";
import themes from "../themes.js";
import { reloadScss } from "./scss.js";
import { setupHyprland } from "./hyprland.js";
import { wallpaper } from "./wallpaper.js";
/** @param {string} name */
export function setTheme(name) {
options.reset();
const theme = themes.find((t) => t.name === name);
if (!theme) return print("No theme named " + name);
options.apply(theme.options);
reloadScss();
setupHyprland();
wallpaper();
}
export const WP = App.configDir + "/assets/";
export const lightColors = {
"theme.scheme": "light",
"color.red": "#d20f39",
"color.green": "#40a02b",
"color.yellow": "#df8e1d",
"color.blue": "#1e66f5",
"color.magenta": "#8839ef",
"color.teal": "#179299",
"color.orange": "#fe640b",
"theme.bg": "transparentize(#eff1f5, 0.3)",
"theme.fg": "#4c4f69",
};
export const darkColors = {
"theme.scheme": "dark",
"color.red": "#e78284",
"color.green": "#a6d189",
"color.yellow": "#e5c890",
"color.blue": "#8caaee",
"color.magenta": "#ca9ee6",
"color.teal": "#81c8be",
"color.orange": "#ef9f76",
"theme.bg": "transparentize(#303446, 0.3)",
"theme.fg": "#c6d0f5",
};
export const Theme = ({ name, icon = " ", ...options }) => ({
name,
icon,
options: {
"theme.name": name,
"theme.icon": icon,
...options,
},
});
let settingsDialog;
export async function openSettings() {
if (settingsDialog) return settingsDialog.present();
try {
settingsDialog = (await import("./SettingsDialog.js")).default;
settingsDialog.present();
} catch (error) {
if (error instanceof Error) console.error(error.message);
}
}

View File

@@ -1,19 +0,0 @@
import options from "../options.js";
import { exec, execAsync } from "resource:///com/github/Aylur/ags/utils.js";
import { dependencies } from "../utils.js";
export function initWallpaper() {
if (dependencies(["swww"])) {
exec("swww init");
options.desktop.wallpaper.img.connect("changed", wallpaper);
}
}
export function wallpaper() {
if (!dependencies(["swww"])) return;
execAsync(["swww", "img", options.desktop.wallpaper.img.value]).catch((err) =>
console.error(err),
);
}

View File

@@ -1,77 +0,0 @@
/**
* A Theme is a set of options that will be applied
* ontop of the default values. see options.js for possible options
*/
import { Theme, WP, lightColors, darkColors } from "./settings/theme.js";
export default [
Theme({
name: "Frappé",
icon: "󰄛",
"desktop.screen_corners": false,
"desktop.clock.enable": false,
"bar.style": "separated",
"bar.separators": false,
"desktop.wallpaper.img":
WP + "wallpapers/Lakeside/lakeside_2019_midnight.png",
...darkColors,
}),
Theme({
name: "Latte",
icon: "󰄛",
"desktop.screen_corners": false,
"desktop.clock.enable": false,
"bar.style": "separated",
"bar.separators": false,
"desktop.wallpaper.img":
WP + "wallpapers/Lakeside/Lakeside_2019_Teal_NoDeer_UHD2.png",
...lightColors,
"theme.widget.bg": "$accent",
"theme.widget.opacity": 64,
}),
/*Theme({
name: "Leaves",
icon: "󰌪",
"desktop.wallpaper.img": WP + "leaves.jpg",
"theme.accent.accent": "$green",
"theme.accent.gradient": "to right, $accent, darken($accent, 14%)",
"theme.widget.opacity": 92,
"border.opacity": 86,
"theme.bg": "transparentize(#171717, 0.3)",
"bar.style": "floating",
radii: 0,
}),
Theme({
name: "Ivory",
icon: "󰟆",
...lightColors,
"desktop.wallpaper.img": WP + "ivory.png",
"desktop.wallpaper.fg": "$bg_color",
"desktop.screen_corners": false,
"bar.style": "separated",
"theme.widget.bg": "$accent",
"theme.widget.opacity": 64,
"desktop.drop_shadow": false,
"border.width": 2,
"border.opacity": 0,
"theme.accent.gradient": "to right, $accent, darken($accent, 6%)",
"hypr.inactive_border": "rgba(111111FF)",
"bar.separators": false,
}),
Theme({
name: "Space",
icon: "",
"desktop.wallpaper.img": WP + "space.jpg",
spacing: 11,
padding: 10,
radii: 12,
"theme.accent.accent": "$magenta",
"desktop.screen_corners": false,
"desktop.clock.enable": false,
"bar.separators": false,
"bar.icon": "",
"theme.bg": "transparentize(#171717, 0.3)",
"theme.widget.opacity": 95,
"bar.flat_buttons": false,
}),*/
];

View File

@@ -1,99 +0,0 @@
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import cairo from "cairo";
import icons from "./icons.js";
import Gdk from "gi://Gdk";
import GLib from "gi://GLib";
/**
* @param {number} length
* @param {number=} start
* @returns {Array<number>}
*/
export function range(length, start = 1) {
return Array.from({ length }, (_, i) => i + start);
}
/**
* @param {Array<[string, string] | string[]>} collection
* @param {string} item
* @returns {string}
*/
export function substitute(collection, item) {
return collection.find(([from]) => from === item)?.[1] || item;
}
/**
* @param {(monitor: number) => any} widget
* @returns {Array<import('types/widgets/window').default>}
*/
export function forMonitors(widget) {
const n = Gdk.Display.get_default()?.get_n_monitors() || 1;
return range(n, 0).map(widget).flat(1);
}
/**
* @param {import('gi://Gtk?version=3.0').default.Widget} widget
* @returns {any} - missing cairo type
*/
export function createSurfaceFromWidget(widget) {
const alloc = widget.get_allocation();
const surface = new cairo.ImageSurface(
cairo.Format.ARGB32,
alloc.width,
alloc.height,
);
const cr = new cairo.Context(surface);
cr.setSourceRGBA(255, 255, 255, 0);
cr.rectangle(0, 0, alloc.width, alloc.height);
cr.fill();
widget.draw(cr);
return surface;
}
/** @param {string} icon */
export function getAudioTypeIcon(icon) {
const substitues = [
["audio-headset-bluetooth", icons.audio.type.headset],
["audio-card-analog-usb", icons.audio.type.speaker],
["audio-card-analog-pci", icons.audio.type.card],
];
return substitute(substitues, icon);
}
/** @param {import('types/service/applications').Application} app */
export function launchApp(app) {
Utils.execAsync(["hyprctl", "dispatch", "exec", `sh -c ${app.executable}`]);
app.frequency += 1;
}
/** @param {Array<string>} bins */
export function dependencies(bins) {
const deps = bins.map((bin) => {
const has = Utils.exec(`which ${bin}`);
if (!has) print(`missing dependency: ${bin}`);
return !!has;
});
return deps.every((has) => has);
}
/** @param {string} img - path to an img file */
export function blurImg(img) {
const cache = Utils.CACHE_DIR + "/media";
return new Promise((resolve) => {
if (!img) resolve("");
const dir = cache + "/blurred";
const blurred = dir + img.substring(cache.length);
if (GLib.file_test(blurred, GLib.FileTest.EXISTS)) return resolve(blurred);
Utils.ensureDirectory(dir);
Utils.execAsync(["convert", img, "-blur", "0x22", blurred])
.then(() => resolve(blurred))
.catch(() => resolve(""));
});
}

Some files were not shown because too many files have changed in this diff Show More