update system
37
flake.lock
generated
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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 { };
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
Before Width: | Height: | Size: 428 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 127 KiB |
|
Before Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 3.9 MiB |
|
Before Width: | Height: | Size: 3.9 MiB |
|
Before Width: | Height: | Size: 2.9 MiB |
|
Before Width: | Height: | Size: 7.2 MiB |
|
Before Width: | Height: | Size: 7.2 MiB |
|
Before Width: | Height: | Size: 3.5 MiB |
|
Before Width: | Height: | Size: 4.7 MiB |
@@ -1,2 +0,0 @@
|
||||
import { default as main } from "./js/main.js";
|
||||
export default main;
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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();
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -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"),
|
||||
});
|
||||
@@ -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,
|
||||
});
|
||||
@@ -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(),
|
||||
}),
|
||||
});
|
||||
@@ -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);
|
||||
}),
|
||||
}),
|
||||
});
|
||||
@@ -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);
|
||||
},
|
||||
});
|
||||
@@ -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 }),
|
||||
});
|
||||
@@ -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()],
|
||||
}),
|
||||
});
|
||||
@@ -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");
|
||||
};
|
||||
@@ -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 || "",
|
||||
),
|
||||
}),
|
||||
});
|
||||
@@ -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;
|
||||
}),
|
||||
}),
|
||||
});
|
||||
@@ -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"),
|
||||
});
|
||||
@@ -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}`;
|
||||
}),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
});
|
||||
@@ -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,
|
||||
],
|
||||
});
|
||||
};
|
||||
@@ -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));
|
||||
@@ -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");
|
||||
@@ -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(),
|
||||
],
|
||||
}),
|
||||
});
|
||||
@@ -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");
|
||||
@@ -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),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
@@ -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(),
|
||||
],
|
||||
}),
|
||||
});
|
||||
@@ -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", "°"),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
@@ -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()],
|
||||
}),
|
||||
}),
|
||||
],
|
||||
});
|
||||
@@ -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(),
|
||||
});
|
||||
@@ -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),
|
||||
],
|
||||
});
|
||||
@@ -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],
|
||||
});
|
||||
};
|
||||
@@ -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"),
|
||||
});
|
||||
};
|
||||
@@ -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: " ",
|
||||
},
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
import pam
|
||||
import sys
|
||||
import getpass
|
||||
|
||||
print(pam.authenticate(getpass.getuser(), sys.argv[1]));
|
||||
@@ -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,
|
||||
},
|
||||
};
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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);
|
||||
}),
|
||||
});
|
||||
@@ -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,
|
||||
});
|
||||
@@ -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);
|
||||
@@ -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],
|
||||
});
|
||||
};
|
||||
@@ -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,
|
||||
],
|
||||
}),
|
||||
});
|
||||
};
|
||||
@@ -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],
|
||||
}),
|
||||
});
|
||||
};
|
||||
@@ -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);
|
||||
@@ -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`);
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -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");
|
||||
@@ -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,
|
||||
});
|
||||
@@ -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(),
|
||||
});
|
||||
@@ -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"],
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -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(),
|
||||
});
|
||||
@@ -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,
|
||||
),
|
||||
});
|
||||
@@ -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(),
|
||||
});
|
||||
@@ -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();
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -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"),
|
||||
],
|
||||
}),
|
||||
});
|
||||
@@ -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,
|
||||
}),
|
||||
}),
|
||||
});
|
||||
@@ -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),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
});
|
||||
@@ -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(),
|
||||
],
|
||||
}),
|
||||
});
|
||||
@@ -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,
|
||||
});
|
||||
@@ -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"),
|
||||
],
|
||||
}),
|
||||
}),
|
||||
],
|
||||
});
|
||||
@@ -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),
|
||||
),
|
||||
}),
|
||||
],
|
||||
});
|
||||
@@ -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(),
|
||||
],
|
||||
});
|
||||
@@ -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],
|
||||
});
|
||||
@@ -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),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
@@ -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),
|
||||
),
|
||||
});
|
||||
@@ -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],
|
||||
});
|
||||
@@ -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")],
|
||||
}),
|
||||
}),
|
||||
],
|
||||
});
|
||||
@@ -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"),
|
||||
],
|
||||
}),
|
||||
}),
|
||||
],
|
||||
});
|
||||
@@ -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(),
|
||||
],
|
||||
});
|
||||
@@ -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),
|
||||
}),
|
||||
);
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
@@ -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],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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"}`,
|
||||
]);
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
]);
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
);
|
||||
}
|
||||
@@ -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,
|
||||
}),*/
|
||||
];
|
||||
@@ -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(""));
|
||||
});
|
||||
}
|
||||