aboutsummaryrefslogtreecommitdiff
path: root/.config/ags/service
diff options
context:
space:
mode:
authorsrdusr <trevorgray@srdusr.com>2024-06-13 13:11:05 +0200
committersrdusr <trevorgray@srdusr.com>2024-06-13 13:11:05 +0200
commitd0fbb19623e4fb6097e1ff3ee49c6a76a0928d0e (patch)
tree937531ddf423d3935c6e20c8a9227e39ce782241 /.config/ags/service
parent4ccbe0270c25ecab492508b5b0209ae53b9c35bd (diff)
downloaddotfiles-d0fbb19623e4fb6097e1ff3ee49c6a76a0928d0e.tar.gz
dotfiles-d0fbb19623e4fb6097e1ff3ee49c6a76a0928d0e.zip
Add ags
Diffstat (limited to '.config/ags/service')
-rw-r--r--.config/ags/service/asusctl.ts52
-rw-r--r--.config/ags/service/brightness.ts69
-rw-r--r--.config/ags/service/colorpicker.ts56
-rw-r--r--.config/ags/service/nix.ts109
-rw-r--r--.config/ags/service/powermenu.ts43
-rw-r--r--.config/ags/service/screenrecord.ts102
-rw-r--r--.config/ags/service/wallpaper.ts98
-rw-r--r--.config/ags/service/weather.ts59
8 files changed, 588 insertions, 0 deletions
diff --git a/.config/ags/service/asusctl.ts b/.config/ags/service/asusctl.ts
new file mode 100644
index 0000000..16acbd7
--- /dev/null
+++ b/.config/ags/service/asusctl.ts
@@ -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
diff --git a/.config/ags/service/brightness.ts b/.config/ags/service/brightness.ts
new file mode 100644
index 0000000..a0b8eb5
--- /dev/null
+++ b/.config/ags/service/brightness.ts
@@ -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
diff --git a/.config/ags/service/colorpicker.ts b/.config/ags/service/colorpicker.ts
new file mode 100644
index 0000000..5918f31
--- /dev/null
+++ b/.config/ags/service/colorpicker.ts
@@ -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
diff --git a/.config/ags/service/nix.ts b/.config/ags/service/nix.ts
new file mode 100644
index 0000000..3bde9fc
--- /dev/null
+++ b/.config/ags/service/nix.ts
@@ -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
diff --git a/.config/ags/service/powermenu.ts b/.config/ags/service/powermenu.ts
new file mode 100644
index 0000000..fd16bc1
--- /dev/null
+++ b/.config/ags/service/powermenu.ts
@@ -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
diff --git a/.config/ags/service/screenrecord.ts b/.config/ags/service/screenrecord.ts
new file mode 100644
index 0000000..58721d2
--- /dev/null
+++ b/.config/ags/service/screenrecord.ts
@@ -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
diff --git a/.config/ags/service/wallpaper.ts b/.config/ags/service/wallpaper.ts
new file mode 100644
index 0000000..0e4bdda
--- /dev/null
+++ b/.config/ags/service/wallpaper.ts
@@ -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
diff --git a/.config/ags/service/weather.ts b/.config/ags/service/weather.ts
new file mode 100644
index 0000000..14f2df2
--- /dev/null
+++ b/.config/ags/service/weather.ts
@@ -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,
+ }>
+ }>
+}