mirror of
https://github.com/Theaninova/TheaninovOS.git
synced 2026-01-04 23:02:48 +00:00
feat: update ags
This commit is contained in:
326
home/desktops/hyprland/ags/js/settings/SettingsDialog.js
Normal file
326
home/desktops/hyprland/ags/js/settings/SettingsDialog.js
Normal file
@@ -0,0 +1,326 @@
|
||||
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({ binds: [["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);
|
||||
},
|
||||
connections: [
|
||||
["value-changed", (self) => opt.setValue(self.value, true)],
|
||||
[opt, (self) => (self.value = opt.value)],
|
||||
],
|
||||
});
|
||||
case "float":
|
||||
case "object":
|
||||
return Widget.Entry({
|
||||
on_accept: (self) => opt.setValue(JSON.parse(self.text || ""), true),
|
||||
connections: [[opt, (self) => (self.text = JSON.stringify(opt.value))]],
|
||||
});
|
||||
case "string":
|
||||
return Widget.Entry({
|
||||
on_accept: (self) => opt.setValue(self.text, true),
|
||||
connections: [[opt, (self) => (self.text = opt.value)]],
|
||||
});
|
||||
case "enum":
|
||||
return EnumSetter(opt);
|
||||
case "boolean":
|
||||
return Widget.Switch({
|
||||
connections: [
|
||||
["notify::active", (self) => opt.setValue(self.active, true)],
|
||||
[opt, (self) => (self.active = opt.value)],
|
||||
],
|
||||
});
|
||||
case "img":
|
||||
return Widget.FileChooserButton({
|
||||
connections: [
|
||||
[
|
||||
"selection-changed",
|
||||
(self) => {
|
||||
opt.setValue(self.get_uri()?.replace("file://", ""), true);
|
||||
},
|
||||
],
|
||||
],
|
||||
});
|
||||
case "font":
|
||||
return Widget.FontButton({
|
||||
show_size: false,
|
||||
use_size: false,
|
||||
connections: [
|
||||
["notify::font", ({ font }) => opt.setValue(font, true)],
|
||||
[opt, (self) => (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",
|
||||
setup: (self) => (self.opt = 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,
|
||||
connections: [
|
||||
[
|
||||
search,
|
||||
(self) => {
|
||||
for (const child of self.children) {
|
||||
child.visible =
|
||||
child.opt.id.includes(search.value) ||
|
||||
child.opt.title.includes(search.value) ||
|
||||
child.opt.note.includes(search.value);
|
||||
}
|
||||
},
|
||||
],
|
||||
],
|
||||
children: optionsList
|
||||
.filter((opt) => opt.category.includes(category))
|
||||
.map(Row),
|
||||
}),
|
||||
});
|
||||
|
||||
const sidebar = Widget.Revealer({
|
||||
binds: [["reveal-child", search, "value", (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,
|
||||
binds: [
|
||||
[
|
||||
"class-name",
|
||||
currentPage,
|
||||
"value",
|
||||
(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",
|
||||
binds: [
|
||||
["reveal-child", showSearch],
|
||||
["transition-duration", options.transition],
|
||||
],
|
||||
child: Widget.Entry({
|
||||
connections: [
|
||||
[
|
||||
showSearch,
|
||||
(self) => {
|
||||
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",
|
||||
items: categories.map((name) => [name, Page(name)]),
|
||||
binds: [
|
||||
["shown", currentPage],
|
||||
["visible", search, "value", (v) => !v],
|
||||
],
|
||||
});
|
||||
|
||||
const searchPage = Widget.Box({
|
||||
binds: [["visible", search, "value", (v) => !!v]],
|
||||
child: Page(""),
|
||||
});
|
||||
|
||||
export default RegularWindow({
|
||||
name: "settings-dialog",
|
||||
title: "Settings",
|
||||
setup: (win) => win.set_default_size(800, 500),
|
||||
connections: [
|
||||
[
|
||||
"delete-event",
|
||||
(win) => {
|
||||
win.hide();
|
||||
return true;
|
||||
},
|
||||
],
|
||||
[
|
||||
"key-press-event",
|
||||
(self, event) => {
|
||||
if (event.get_keyval()[1] === imports.gi.Gdk.KEY_Escape) {
|
||||
self.text = "";
|
||||
showSearch.setValue(false);
|
||||
search.setValue("");
|
||||
}
|
||||
},
|
||||
],
|
||||
],
|
||||
child: Widget.Box({
|
||||
children: [
|
||||
sidebar,
|
||||
Widget.Box({
|
||||
vertical: true,
|
||||
children: [searchEntry, categoriesStack, searchPage],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
});
|
||||
37
home/desktops/hyprland/ags/js/settings/globals.js
Normal file
37
home/desktops/hyprland/ags/js/settings/globals.js
Normal file
@@ -0,0 +1,37 @@
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
76
home/desktops/hyprland/ags/js/settings/hyprland.js
Normal file
76
home/desktops/hyprland/ags/js/settings/hyprland.js
Normal file
@@ -0,0 +1,76 @@
|
||||
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 { readFile, writeFile } 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() {
|
||||
if (readFile("/tmp/ags/hyprland-init")) return;
|
||||
|
||||
sendBatch(
|
||||
Array.from(App.windows).flatMap(([name]) => [
|
||||
`layerrule blur, ${name}`,
|
||||
noIgnorealpha.some((skip) => name.includes(skip))
|
||||
? ""
|
||||
: `layerrule ignorealpha 0.6, ${name}`,
|
||||
]),
|
||||
);
|
||||
|
||||
writeFile("init", "/tmp/ags/hyprland-init");
|
||||
}
|
||||
|
||||
export async function setupHyprland() {
|
||||
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 bar_style = options.bar.style.value;
|
||||
const bar_pos = options.bar.position.value;
|
||||
const inactive_border = options.hypr.inactive_border.value;
|
||||
const accent = getColor(options.theme.accent.accent.value);
|
||||
|
||||
const batch = [];
|
||||
|
||||
JSON.parse(await Hyprland.sendMessage("j/monitors")).forEach(({ name }) => {
|
||||
const v = bar_pos === "top" ? `-${wm_gaps},0,0,0` : `0,-${wm_gaps},0,0`;
|
||||
if (bar_style !== "normal") batch.push(`monitor ${name},addreserved,${v}`);
|
||||
else batch.push(`monitor ${name},addreserved,0,0,0,0`);
|
||||
});
|
||||
|
||||
batch.push(
|
||||
`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"}`,
|
||||
);
|
||||
|
||||
sendBatch(batch);
|
||||
}
|
||||
198
home/desktops/hyprland/ags/js/settings/option.js
Normal file
198
home/desktops/hyprland/ags/js/settings/option.js
Normal file
@@ -0,0 +1,198 @@
|
||||
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, { Binding } 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]);
|
||||
}
|
||||
}
|
||||
65
home/desktops/hyprland/ags/js/settings/scss.js
Normal file
65
home/desktops/hyprland/ags/js/settings/scss.js
Normal file
@@ -0,0 +1,65 @@
|
||||
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";
|
||||
import { dependencies } from "../utils.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() {
|
||||
if (!dependencies(["sassc"])) return;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
114
home/desktops/hyprland/ags/js/settings/setup.js
Normal file
114
home/desktops/hyprland/ags/js/settings/setup.js
Normal file
@@ -0,0 +1,114 @@
|
||||
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, scssWatcher } 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";
|
||||
|
||||
export function init() {
|
||||
notificationBlacklist();
|
||||
warnOnLowBattery();
|
||||
globals();
|
||||
tmux();
|
||||
gsettigsColorScheme();
|
||||
gtkFontSettings();
|
||||
scssWatcher();
|
||||
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 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",
|
||||
]);
|
||||
});
|
||||
}
|
||||
55
home/desktops/hyprland/ags/js/settings/theme.js
Normal file
55
home/desktops/hyprland/ags/js/settings/theme.js
Normal file
@@ -0,0 +1,55 @@
|
||||
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": "#e55f86",
|
||||
"color.green": "#00D787",
|
||||
"color.yellow": "#EBFF71",
|
||||
"color.blue": "#51a4e7",
|
||||
"color.magenta": "#9077e7",
|
||||
"color.teal": "#51e6e6",
|
||||
"color.orange": "#E79E64",
|
||||
"theme.bg": "#fffffa",
|
||||
"theme.fg": "#141414",
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
25
home/desktops/hyprland/ags/js/settings/wallpaper.js
Normal file
25
home/desktops/hyprland/ags/js/settings/wallpaper.js
Normal file
@@ -0,0 +1,25 @@
|
||||
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",
|
||||
"--transition-type",
|
||||
"grow",
|
||||
"--transition-pos",
|
||||
exec("hyprctl cursorpos").replace(" ", ""),
|
||||
options.desktop.wallpaper.img.value,
|
||||
]).catch((err) => console.error(err));
|
||||
}
|
||||
Reference in New Issue
Block a user