Added ags
TODO copy ags config
This commit is contained in:
52
roles/ags/files/service/asusctl.ts
Normal file
52
roles/ags/files/service/asusctl.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { sh } from "lib/utils"
|
||||
|
||||
type Profile = "Performance" | "Balanced" | "Quiet"
|
||||
type Mode = "Hybrid" | "Integrated"
|
||||
|
||||
class Asusctl extends Service {
|
||||
static {
|
||||
Service.register(this, {}, {
|
||||
"profile": ["string", "r"],
|
||||
"mode": ["string", "r"],
|
||||
})
|
||||
}
|
||||
|
||||
available = !!Utils.exec("which asusctl")
|
||||
#profile: Profile = "Balanced"
|
||||
#mode: Mode = "Hybrid"
|
||||
|
||||
async nextProfile() {
|
||||
await sh("asusctl profile -n")
|
||||
const profile = await sh("asusctl profile -p")
|
||||
const p = profile.split(" ")[3] as Profile
|
||||
this.#profile = p
|
||||
this.changed("profile")
|
||||
}
|
||||
|
||||
async setProfile(prof: Profile) {
|
||||
await sh(`asusctl profile --profile-set ${prof}`)
|
||||
this.#profile = prof
|
||||
this.changed("profile")
|
||||
}
|
||||
|
||||
async nextMode() {
|
||||
await sh(`supergfxctl -m ${this.#mode === "Hybrid" ? "Integrated" : "Hybrid"}`)
|
||||
this.#mode = await sh("supergfxctl -g") as Mode
|
||||
this.changed("profile")
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
|
||||
if (this.available) {
|
||||
sh("asusctl profile -p").then(p => this.#profile = p.split(" ")[3] as Profile)
|
||||
sh("supergfxctl -g").then(m => this.#mode = m as Mode)
|
||||
}
|
||||
}
|
||||
|
||||
get profiles(): Profile[] { return ["Performance", "Balanced", "Quiet"] }
|
||||
get profile() { return this.#profile }
|
||||
get mode() { return this.#mode }
|
||||
}
|
||||
|
||||
export default new Asusctl
|
||||
69
roles/ags/files/service/brightness.ts
Normal file
69
roles/ags/files/service/brightness.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { bash, dependencies, sh } from "lib/utils"
|
||||
|
||||
if (!dependencies("brightnessctl"))
|
||||
App.quit()
|
||||
|
||||
const get = (args: string) => Number(Utils.exec(`brightnessctl ${args}`))
|
||||
const screen = await bash`ls -w1 /sys/class/backlight | head -1`
|
||||
const kbd = await bash`ls -w1 /sys/class/leds | head -1`
|
||||
|
||||
class Brightness extends Service {
|
||||
static {
|
||||
Service.register(this, {}, {
|
||||
"screen": ["float", "rw"],
|
||||
"kbd": ["int", "rw"],
|
||||
})
|
||||
}
|
||||
|
||||
#kbdMax = get(`--device ${kbd} max`)
|
||||
#kbd = get(`--device ${kbd} get`)
|
||||
#screenMax = get("max")
|
||||
#screen = get("get") / get("max")
|
||||
|
||||
get kbd() { return this.#kbd }
|
||||
get screen() { return this.#screen }
|
||||
|
||||
set kbd(value) {
|
||||
if (value < 0 || value > this.#kbdMax)
|
||||
return
|
||||
|
||||
sh(`brightnessctl -d ${kbd} s ${value} -q`).then(() => {
|
||||
this.#kbd = value
|
||||
this.changed("kbd")
|
||||
})
|
||||
}
|
||||
|
||||
set screen(percent) {
|
||||
if (percent < 0)
|
||||
percent = 0
|
||||
|
||||
if (percent > 1)
|
||||
percent = 1
|
||||
|
||||
sh(`brightnessctl set ${Math.floor(percent * 100)}% -q`).then(() => {
|
||||
this.#screen = percent
|
||||
this.changed("screen")
|
||||
})
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
|
||||
const screenPath = `/sys/class/backlight/${screen}/brightness`
|
||||
const kbdPath = `/sys/class/leds/${kbd}/brightness`
|
||||
|
||||
Utils.monitorFile(screenPath, async f => {
|
||||
const v = await Utils.readFileAsync(f)
|
||||
this.#screen = Number(v) / this.#screenMax
|
||||
this.changed("screen")
|
||||
})
|
||||
|
||||
Utils.monitorFile(kbdPath, async f => {
|
||||
const v = await Utils.readFileAsync(f)
|
||||
this.#kbd = Number(v) / this.#kbdMax
|
||||
this.changed("kbd")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default new Brightness
|
||||
56
roles/ags/files/service/colorpicker.ts
Normal file
56
roles/ags/files/service/colorpicker.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import icons from "lib/icons"
|
||||
import { bash, dependencies } from "lib/utils"
|
||||
|
||||
const COLORS_CACHE = Utils.CACHE_DIR + "/colorpicker.json"
|
||||
const MAX_NUM_COLORS = 10
|
||||
|
||||
class ColorPicker extends Service {
|
||||
static {
|
||||
Service.register(this, {}, {
|
||||
"colors": ["jsobject"],
|
||||
})
|
||||
}
|
||||
|
||||
#notifID = 0
|
||||
#colors = JSON.parse(Utils.readFile(COLORS_CACHE) || "[]") as string[]
|
||||
|
||||
get colors() { return [...this.#colors] }
|
||||
set colors(colors) {
|
||||
this.#colors = colors
|
||||
this.changed("colors")
|
||||
}
|
||||
|
||||
// TODO: doesn't work?
|
||||
async wlCopy(color: string) {
|
||||
if (dependencies("wl-copy"))
|
||||
bash(`wl-copy ${color}`)
|
||||
}
|
||||
|
||||
readonly pick = async () => {
|
||||
if (!dependencies("hyprpicker"))
|
||||
return
|
||||
|
||||
const color = await bash("hyprpicker -a -r")
|
||||
if (!color)
|
||||
return
|
||||
|
||||
this.wlCopy(color)
|
||||
const list = this.colors
|
||||
if (!list.includes(color)) {
|
||||
list.push(color)
|
||||
if (list.length > MAX_NUM_COLORS)
|
||||
list.shift()
|
||||
|
||||
this.colors = list
|
||||
Utils.writeFile(JSON.stringify(list, null, 2), COLORS_CACHE)
|
||||
}
|
||||
|
||||
this.#notifID = await Utils.notify({
|
||||
id: this.#notifID,
|
||||
iconName: icons.ui.colorpicker,
|
||||
summary: color,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default new ColorPicker
|
||||
109
roles/ags/files/service/nix.ts
Normal file
109
roles/ags/files/service/nix.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import icons from "lib/icons"
|
||||
import { bash, dependencies } from "lib/utils"
|
||||
import options from "options"
|
||||
|
||||
const CACHE = `${Utils.CACHE_DIR}/nixpkgs`
|
||||
const PREFIX = "legacyPackages.x86_64-linux."
|
||||
const MAX = options.launcher.nix.max
|
||||
const nixpkgs = options.launcher.nix.pkgs
|
||||
|
||||
export type Nixpkg = {
|
||||
name: string
|
||||
description: string
|
||||
pname: string
|
||||
version: string
|
||||
}
|
||||
|
||||
class Nix extends Service {
|
||||
static {
|
||||
Service.register(this, {}, {
|
||||
"available": ["boolean", "r"],
|
||||
"ready": ["boolean", "rw"],
|
||||
})
|
||||
}
|
||||
|
||||
#db: { [name: string]: Nixpkg } = {}
|
||||
#ready = true
|
||||
|
||||
private set ready(r: boolean) {
|
||||
this.#ready = r
|
||||
this.changed("ready")
|
||||
}
|
||||
|
||||
get db() { return this.#db }
|
||||
get ready() { return this.#ready }
|
||||
get available() { return Utils.exec("which nix") }
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
if (!this.available)
|
||||
return this
|
||||
|
||||
this.#updateList()
|
||||
nixpkgs.connect("changed", this.#updateList)
|
||||
}
|
||||
|
||||
query = async (filter: string) => {
|
||||
if (!dependencies("fzf", "nix") || !this.#ready)
|
||||
return [] as string[]
|
||||
|
||||
return bash(`cat ${CACHE} | fzf -f ${filter} -e | head -n ${MAX} `)
|
||||
.then(str => str.split("\n").filter(i => i))
|
||||
}
|
||||
|
||||
nix(cmd: string, bin: string, args: string) {
|
||||
return Utils.execAsync(`nix ${cmd} ${nixpkgs}#${bin} --impure ${args}`)
|
||||
}
|
||||
|
||||
run = async (input: string) => {
|
||||
if (!dependencies("nix"))
|
||||
return
|
||||
|
||||
try {
|
||||
const [bin, ...args] = input.trim().split(/\s+/)
|
||||
|
||||
this.ready = false
|
||||
await this.nix("shell", bin, "--command sh -c 'exit'")
|
||||
this.ready = true
|
||||
|
||||
this.nix("run", bin, ["--", ...args].join(" "))
|
||||
} catch (err) {
|
||||
if (typeof err === "string")
|
||||
Utils.notify("NixRun Error", err, icons.nix.nix)
|
||||
else
|
||||
logError(err)
|
||||
} finally {
|
||||
this.ready = true
|
||||
}
|
||||
}
|
||||
|
||||
#updateList = async () => {
|
||||
if (!dependencies("nix"))
|
||||
return
|
||||
|
||||
this.ready = false
|
||||
this.#db = {}
|
||||
|
||||
// const search = await bash(`nix search ${nixpkgs} --json`)
|
||||
const search = ""
|
||||
if (!search) {
|
||||
this.ready = true
|
||||
return
|
||||
}
|
||||
|
||||
const json = Object.entries(JSON.parse(search) as {
|
||||
[name: string]: Nixpkg
|
||||
})
|
||||
|
||||
for (const [pkg, info] of json) {
|
||||
const name = pkg.replace(PREFIX, "")
|
||||
this.#db[name] = { ...info, name }
|
||||
}
|
||||
|
||||
const list = Object.keys(this.#db).join("\n")
|
||||
await Utils.writeFile(list, CACHE)
|
||||
this.ready = true
|
||||
}
|
||||
}
|
||||
|
||||
export default new Nix
|
||||
43
roles/ags/files/service/powermenu.ts
Normal file
43
roles/ags/files/service/powermenu.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import options from "options"
|
||||
|
||||
const { sleep, reboot, logout, shutdown } = options.powermenu
|
||||
|
||||
export type Action = "sleep" | "reboot" | "logout" | "shutdown"
|
||||
|
||||
class PowerMenu extends Service {
|
||||
static {
|
||||
Service.register(this, {}, {
|
||||
"title": ["string"],
|
||||
"cmd": ["string"],
|
||||
})
|
||||
}
|
||||
|
||||
#title = ""
|
||||
#cmd = ""
|
||||
|
||||
get title() { return this.#title }
|
||||
get cmd() { return this.#cmd }
|
||||
|
||||
action(action: Action) {
|
||||
[this.#cmd, this.#title] = {
|
||||
sleep: [sleep.value, "Sleep"],
|
||||
reboot: [reboot.value, "Reboot"],
|
||||
logout: [logout.value, "Log Out"],
|
||||
shutdown: [shutdown.value, "Shutdown"],
|
||||
}[action]
|
||||
|
||||
this.notify("cmd")
|
||||
this.notify("title")
|
||||
this.emit("changed")
|
||||
App.closeWindow("powermenu")
|
||||
App.openWindow("verification")
|
||||
}
|
||||
|
||||
readonly shutdown = () => {
|
||||
this.action("shutdown")
|
||||
}
|
||||
}
|
||||
|
||||
const powermenu = new PowerMenu
|
||||
Object.assign(globalThis, { powermenu })
|
||||
export default powermenu
|
||||
102
roles/ags/files/service/screenrecord.ts
Normal file
102
roles/ags/files/service/screenrecord.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import GLib from "gi://GLib"
|
||||
import icons from "lib/icons"
|
||||
import { dependencies, sh, bash } from "lib/utils"
|
||||
|
||||
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"],
|
||||
})
|
||||
}
|
||||
|
||||
#recordings = Utils.HOME + "/Videos/Screencasting"
|
||||
#screenshots = Utils.HOME + "/Pictures/Screenshots"
|
||||
#file = ""
|
||||
#interval = 0
|
||||
|
||||
recording = false
|
||||
timer = 0
|
||||
|
||||
async start() {
|
||||
if (!dependencies("slurp", "wf-recorder"))
|
||||
return
|
||||
|
||||
if (this.recording)
|
||||
return
|
||||
|
||||
Utils.ensureDirectory(this.#recordings)
|
||||
this.#file = `${this.#recordings}/${now()}.mp4`
|
||||
sh(`wf-recorder -g "${await sh("slurp")}" -f ${this.#file} --pixel-format yuv420p`)
|
||||
|
||||
this.recording = true
|
||||
this.changed("recording")
|
||||
|
||||
this.timer = 0
|
||||
this.#interval = Utils.interval(1000, () => {
|
||||
this.changed("timer")
|
||||
this.timer++
|
||||
})
|
||||
}
|
||||
|
||||
async stop() {
|
||||
if (!this.recording)
|
||||
return
|
||||
|
||||
await bash("killall -INT wf-recorder")
|
||||
this.recording = false
|
||||
this.changed("recording")
|
||||
GLib.source_remove(this.#interval)
|
||||
|
||||
Utils.notify({
|
||||
iconName: icons.fallback.video,
|
||||
summary: "Screenrecord",
|
||||
body: this.#file,
|
||||
actions: {
|
||||
"Show in Files": () => sh(`xdg-open ${this.#recordings}`),
|
||||
"View": () => sh(`xdg-open ${this.#file}`),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async screenshot(full = false) {
|
||||
if (!dependencies("slurp", "wayshot"))
|
||||
return
|
||||
|
||||
const file = `${this.#screenshots}/${now()}.png`
|
||||
Utils.ensureDirectory(this.#screenshots)
|
||||
|
||||
if (full) {
|
||||
await sh(`wayshot -f ${file}`)
|
||||
}
|
||||
else {
|
||||
const size = await sh("slurp")
|
||||
if (!size)
|
||||
return
|
||||
|
||||
await sh(`wayshot -f ${file} -s "${size}"`)
|
||||
}
|
||||
|
||||
bash(`wl-copy < ${file}`)
|
||||
|
||||
Utils.notify({
|
||||
image: file,
|
||||
summary: "Screenshot",
|
||||
body: file,
|
||||
actions: {
|
||||
"Show in Files": () => sh(`xdg-open ${this.#screenshots}`),
|
||||
"View": () => sh(`xdg-open ${file}`),
|
||||
"Edit": () => {
|
||||
if (dependencies("swappy"))
|
||||
sh(`swappy -f ${file}`)
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const recorder = new Recorder
|
||||
Object.assign(globalThis, { recorder })
|
||||
export default recorder
|
||||
98
roles/ags/files/service/wallpaper.ts
Normal file
98
roles/ags/files/service/wallpaper.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import options from "options"
|
||||
import { dependencies, sh } from "lib/utils"
|
||||
|
||||
export type Resolution = 1920 | 1366 | 3840
|
||||
export type Market =
|
||||
| "random"
|
||||
| "en-US"
|
||||
| "ja-JP"
|
||||
| "en-AU"
|
||||
| "en-GB"
|
||||
| "de-DE"
|
||||
| "en-NZ"
|
||||
| "en-CA"
|
||||
|
||||
const WP = `${Utils.HOME}/.config/background`
|
||||
const Cache = `${Utils.HOME}/Pictures/Wallpapers/Bing`
|
||||
|
||||
class Wallpaper extends Service {
|
||||
static {
|
||||
Service.register(this, {}, {
|
||||
"wallpaper": ["string"],
|
||||
})
|
||||
}
|
||||
|
||||
#blockMonitor = false
|
||||
|
||||
#wallpaper() {
|
||||
if (!dependencies("swww"))
|
||||
return
|
||||
|
||||
sh("hyprctl cursorpos").then(pos => {
|
||||
sh([
|
||||
"swww", "img",
|
||||
"--transition-type", "grow",
|
||||
"--transition-pos", pos.replace(" ", ""),
|
||||
WP,
|
||||
]).then(() => {
|
||||
this.changed("wallpaper")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async #setWallpaper(path: string) {
|
||||
this.#blockMonitor = true
|
||||
|
||||
await sh(`cp ${path} ${WP}`)
|
||||
this.#wallpaper()
|
||||
|
||||
this.#blockMonitor = false
|
||||
}
|
||||
|
||||
async #fetchBing() {
|
||||
const res = await Utils.fetch("https://bing.biturl.top/", {
|
||||
params: {
|
||||
resolution: options.wallpaper.resolution.value,
|
||||
format: "json",
|
||||
image_format: "jpg",
|
||||
index: "random",
|
||||
mkt: options.wallpaper.market.value,
|
||||
},
|
||||
}).then(res => res.text())
|
||||
|
||||
if (!res.startsWith("{"))
|
||||
return console.warn("bing api", res)
|
||||
|
||||
const { url } = JSON.parse(res)
|
||||
const file = `${Cache}/${url.replace("https://www.bing.com/th?id=", "")}`
|
||||
|
||||
if (dependencies("curl")) {
|
||||
Utils.ensureDirectory(Cache)
|
||||
await sh(`curl "${url}" --output ${file}`)
|
||||
this.#setWallpaper(file)
|
||||
}
|
||||
}
|
||||
|
||||
readonly random = () => { this.#fetchBing() }
|
||||
readonly set = (path: string) => { this.#setWallpaper(path) }
|
||||
get wallpaper() { return WP }
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
|
||||
if (!dependencies("swww"))
|
||||
return this
|
||||
|
||||
// gtk portal
|
||||
Utils.monitorFile(WP, () => {
|
||||
if (!this.#blockMonitor)
|
||||
this.#wallpaper()
|
||||
})
|
||||
|
||||
Utils.execAsync("swww-daemon")
|
||||
.then(this.#wallpaper)
|
||||
.catch(() => null)
|
||||
}
|
||||
}
|
||||
|
||||
export default new Wallpaper
|
||||
59
roles/ags/files/service/weather.ts
Normal file
59
roles/ags/files/service/weather.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import options from "options"
|
||||
|
||||
const { interval, key, cities, unit } = options.datemenu.weather
|
||||
|
||||
class Weather extends Service {
|
||||
static {
|
||||
Service.register(this, {}, {
|
||||
"forecasts": ["jsobject"],
|
||||
})
|
||||
}
|
||||
|
||||
#forecasts: Forecast[] = []
|
||||
get forecasts() { return this.#forecasts }
|
||||
|
||||
async #fetch(placeid: number) {
|
||||
const url = "https://api.openweathermap.org/data/2.5/forecast"
|
||||
const res = await Utils.fetch(url, {
|
||||
params: {
|
||||
id: placeid,
|
||||
appid: key.value,
|
||||
untis: unit.value,
|
||||
},
|
||||
})
|
||||
return await res.json()
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
if (!key.value)
|
||||
return this
|
||||
|
||||
Utils.interval(interval.value, () => {
|
||||
Promise.all(cities.value.map(this.#fetch)).then(forecasts => {
|
||||
this.#forecasts = forecasts as Forecast[]
|
||||
this.changed("forecasts")
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default new Weather
|
||||
|
||||
type Forecast = {
|
||||
city: {
|
||||
name: string,
|
||||
}
|
||||
list: Array<{
|
||||
dt: number
|
||||
main: {
|
||||
temp: number
|
||||
feels_like: number
|
||||
},
|
||||
weather: Array<{
|
||||
main: string,
|
||||
description: string,
|
||||
icon: string,
|
||||
}>
|
||||
}>
|
||||
}
|
||||
Reference in New Issue
Block a user