aboutsummaryrefslogtreecommitdiff
path: root/.config/awesome
diff options
context:
space:
mode:
authordelta <darkussdelta@gmail.com>2025-07-04 00:38:29 +0200
committerdelta <darkussdelta@gmail.com>2025-07-04 00:38:29 +0200
commitb3530d7c4a102935fa26498a160ee1dc6c1e9c03 (patch)
treed7751206a694bc5de2d6b34b0c077cfcd1855798 /.config/awesome
parentdf75ec5ed5e3848c497f0439acb43ec9246ad3e7 (diff)
:3
Diffstat (limited to '.config/awesome')
-rw-r--r--.config/awesome/.nvim.lua2
-rw-r--r--.config/awesome/.styluaignore2
-rw-r--r--.config/awesome/assets/custom/init.lua6
-rw-r--r--.config/awesome/assets/custom/vinyl-record-fill.svg17
-rw-r--r--.config/awesome/assets/phosphor/browser-fill.svg2
-rw-r--r--.config/awesome/assets/phosphor/calendar-dots-fill.svg1
-rw-r--r--.config/awesome/assets/phosphor/caret-down-bold.svg1
-rw-r--r--.config/awesome/assets/phosphor/caret-up-bold.svg1
-rw-r--r--.config/awesome/assets/phosphor/init.lua12
-rw-r--r--.config/awesome/assets/phosphor/speaker-hifi-fill.svg1
-rw-r--r--.config/awesome/assets/phosphor/timer-fill.svg1
-rw-r--r--.config/awesome/assets/phosphor/waveform-fill.svg1
-rw-r--r--.config/awesome/assets/simpleicons/firefox.svg1
-rw-r--r--.config/awesome/assets/simpleicons/init.lua6
-rw-r--r--.config/awesome/assets/simpleicons/librewolf.svg1
-rw-r--r--.config/awesome/assets/simpleicons/spotify.svg1
-rw-r--r--.config/awesome/curious.lua36
m---------.config/awesome/lib/lit0
-rw-r--r--.config/awesome/misc/autostart.lua11
-rw-r--r--.config/awesome/misc/cfg.lua13
-rw-r--r--.config/awesome/misc/keys.lua157
-rw-r--r--.config/awesome/misc/rules.lua39
-rw-r--r--.config/awesome/prismite.lua45
-rw-r--r--.config/awesome/quarrel/animation/bezier.lua89
-rw-r--r--.config/awesome/quarrel/animation/init.lua249
-rw-r--r--.config/awesome/quarrel/animation/subscribable/init.lua47
-rw-r--r--.config/awesome/quarrel/animation/tween/init.lua554
-rw-r--r--.config/awesome/quarrel/bezier.lua343
-rw-r--r--.config/awesome/quarrel/bind/consts.lua27
-rw-r--r--.config/awesome/quarrel/bind/init.lua (renamed from .config/awesome/quarrel/bind.lua)31
-rw-r--r--.config/awesome/quarrel/color.lua64
-rw-r--r--.config/awesome/quarrel/const.lua22
-rw-r--r--.config/awesome/quarrel/debugger.lua865
-rw-r--r--.config/awesome/quarrel/delegate.lua10
-rw-r--r--.config/awesome/quarrel/fs.lua21
-rw-r--r--.config/awesome/quarrel/iconset.lua21
-rw-r--r--.config/awesome/quarrel/init.lua10
-rw-r--r--.config/awesome/quarrel/lua-rust.tar.gzbin18878 -> 0 bytes
-rw-r--r--.config/awesome/quarrel/markup.lua10
-rw-r--r--.config/awesome/quarrel/math/consts.lua5
-rw-r--r--.config/awesome/quarrel/math/init.lua (renamed from .config/awesome/quarrel/math.lua)14
-rw-r--r--.config/awesome/quarrel/native/Cargo.toml19
-rw-r--r--.config/awesome/quarrel/native/init.lua4
-rw-r--r--.config/awesome/quarrel/native/src/http.rs22
-rw-r--r--.config/awesome/quarrel/native/src/lenses/application.rs85
-rw-r--r--.config/awesome/quarrel/native/src/lenses/calculator.rs47
-rw-r--r--.config/awesome/quarrel/native/src/lenses/entry.rs19
-rw-r--r--.config/awesome/quarrel/native/src/lenses/mod.rs78
-rw-r--r--.config/awesome/quarrel/native/src/lib.rs14
-rw-r--r--.config/awesome/quarrel/native/src/moondrop.rs3
-rw-r--r--.config/awesome/quarrel/native/src/mpd.rs142
-rw-r--r--.config/awesome/quarrel/native/src/net/mod.rs4
-rw-r--r--.config/awesome/quarrel/native/src/util.rs3
-rw-r--r--.config/awesome/quarrel/persistent.lua62
-rw-r--r--.config/awesome/quarrel/store.lua4
-rw-r--r--.config/awesome/quarrel/ui.lua145
-rw-r--r--.config/awesome/quarrel/ui/consts.lua24
-rw-r--r--.config/awesome/quarrel/ui/init.lua167
-rw-r--r--.config/awesome/quarrel/vars.lua254
-rw-r--r--.config/awesome/rc.lua1
-rw-r--r--.config/awesome/services/backlight.lua6
-rw-r--r--.config/awesome/services/battery.lua8
-rw-r--r--.config/awesome/services/common.lua60
-rw-r--r--.config/awesome/services/mpris/init.lua100
-rw-r--r--.config/awesome/services/playerctl.lua612
-rw-r--r--.config/awesome/signals/client.lua10
-rw-r--r--.config/awesome/ui/conductor/init.lua60
-rw-r--r--.config/awesome/ui/decorations/wallpaper.lua16
-rw-r--r--.config/awesome/ui/fresnel/init.lua104
-rw-r--r--.config/awesome/ui/init.lua4
-rw-r--r--.config/awesome/ui/insightful/init.lua24
-rw-r--r--.config/awesome/ui/osd.lua102
-rw-r--r--.config/awesome/ui/osd/init.lua102
-rw-r--r--.config/awesome/ui/powermenu/init.lua29
-rw-r--r--.config/awesome/ui/statusbar/consts.lua8
-rw-r--r--.config/awesome/ui/statusbar/init.lua68
-rw-r--r--.config/awesome/ui/statusbar/panel/init.lua76
-rw-r--r--.config/awesome/ui/statusbar/panel/widgets/battery.lua1
-rw-r--r--.config/awesome/ui/statusbar/panel/widgets/calendar.lua172
-rw-r--r--.config/awesome/ui/statusbar/panel/widgets/displays.lua78
-rw-r--r--.config/awesome/ui/statusbar/panel/widgets/imagebox.lua720
-rw-r--r--.config/awesome/ui/statusbar/panel/widgets/mpris.lua429
-rw-r--r--.config/awesome/ui/statusbar/panel/widgets/music.old.lua (renamed from .config/awesome/ui/statusbar/panel/widgets/music.lua)35
-rw-r--r--.config/awesome/ui/statusbar/panel/widgets/power_menu.lua16
-rw-r--r--.config/awesome/ui/statusbar/panel/widgets/weather.lua0
-rw-r--r--.config/awesome/ui/statusbar/panel/widgets/wifi.lua27
-rw-r--r--.config/awesome/ui/statusbar/widgets/displays.lua24
-rw-r--r--.config/awesome/ui/statusbar/widgets/taglist.lua32
-rw-r--r--.config/awesome/ui/statusbar/widgets/taglist_new.lua147
-rw-r--r--.config/awesome/ui/statusbar/widgets/tasklist.lua30
-rw-r--r--.config/awesome/ui/wicked/consts.lua5
-rw-r--r--.config/awesome/ui/wicked/init.lua80
92 files changed, 4776 insertions, 2245 deletions
diff --git a/.config/awesome/.nvim.lua b/.config/awesome/.nvim.lua
new file mode 100644
index 0000000..9c7049d
--- /dev/null
+++ b/.config/awesome/.nvim.lua
@@ -0,0 +1,2 @@
+vim.env.GIT_DIR = vim.fn.expand("~/.dots")
+vim.env.GIT_WORK_TREE = vim.fn.expand("~")
diff --git a/.config/awesome/.styluaignore b/.config/awesome/.styluaignore
new file mode 100644
index 0000000..cbf9e27
--- /dev/null
+++ b/.config/awesome/.styluaignore
@@ -0,0 +1,2 @@
+lib/bling
+lib/rubato
diff --git a/.config/awesome/assets/custom/init.lua b/.config/awesome/assets/custom/init.lua
new file mode 100644
index 0000000..fd1dca5
--- /dev/null
+++ b/.config/awesome/assets/custom/init.lua
@@ -0,0 +1,6 @@
+local gfs = require "gears.filesystem"
+local qiconset = require "quarrel.iconset"
+
+local simpleicons_dir = gfs.get_configuration_dir() .. "assets/custom/"
+
+return qiconset(simpleicons_dir)
diff --git a/.config/awesome/assets/custom/vinyl-record-fill.svg b/.config/awesome/assets/custom/vinyl-record-fill.svg
new file mode 100644
index 0000000..0d68cbf
--- /dev/null
+++ b/.config/awesome/assets/custom/vinyl-record-fill.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ width="28"
+ height="28"
+ fill="#000000"
+ viewBox="0 0 256 256"
+ version="1.1"
+ id="svg1"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <defs
+ id="defs1" />
+ <path
+ d="M 128,0 A 128,128 0 1 0 256,128 128.13538,128.13538 0 0 0 128,0 Z M 59.076923,128 A 9.846154,9.8461541 0 0 1 39.384615,128 88.713846,88.713846 0 0 1 128,39.384615 9.846154,9.846154 0 0 1 128,59.076923 68.996923,68.996923 0 0 0 59.076923,128 Z m 39.384615,0 A 29.538462,29.538462 0 1 1 128,157.53846 29.538462,29.538462 0 0 1 98.461538,128 Z M 128,216.61538 a 9.8461538,9.8461538 0 0 1 0,-19.6923 A 68.996923,68.996923 0 0 0 196.92308,128 9.8461538,9.8461538 0 0 1 216.61538,128 88.713846,88.713846 0 0 1 128,216.61538 Z"
+ id="path1"
+ style="stroke-width:1.23077" />
+</svg>
diff --git a/.config/awesome/assets/phosphor/browser-fill.svg b/.config/awesome/assets/phosphor/browser-fill.svg
index 68d95e5..1f64b69 100644
--- a/.config/awesome/assets/phosphor/browser-fill.svg
+++ b/.config/awesome/assets/phosphor/browser-fill.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" fill="#000000" viewBox="0 0 256 256"><path d="M216,40H40A16,16,0,0,0,24,56V200a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A16,16,0,0,0,216,40Zm0,16V88H40V56Z"></path></svg> \ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M216,40H40A16,16,0,0,0,24,56V200a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A16,16,0,0,0,216,40Zm0,16V88H40V56Z"></path></svg> \ No newline at end of file
diff --git a/.config/awesome/assets/phosphor/calendar-dots-fill.svg b/.config/awesome/assets/phosphor/calendar-dots-fill.svg
new file mode 100644
index 0000000..1c25fff
--- /dev/null
+++ b/.config/awesome/assets/phosphor/calendar-dots-fill.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M208,32H184V24a8,8,0,0,0-16,0v8H88V24a8,8,0,0,0-16,0v8H48A16,16,0,0,0,32,48V208a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V48A16,16,0,0,0,208,32ZM84,184a12,12,0,1,1,12-12A12,12,0,0,1,84,184Zm44,0a12,12,0,1,1,12-12A12,12,0,0,1,128,184Zm0-40a12,12,0,1,1,12-12A12,12,0,0,1,128,144Zm44,40a12,12,0,1,1,12-12A12,12,0,0,1,172,184Zm0-40a12,12,0,1,1,12-12A12,12,0,0,1,172,144Zm36-64H48V48H72v8a8,8,0,0,0,16,0V48h80v8a8,8,0,0,0,16,0V48h24Z"></path></svg> \ No newline at end of file
diff --git a/.config/awesome/assets/phosphor/caret-down-bold.svg b/.config/awesome/assets/phosphor/caret-down-bold.svg
new file mode 100644
index 0000000..cc995f8
--- /dev/null
+++ b/.config/awesome/assets/phosphor/caret-down-bold.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M216.49,104.49l-80,80a12,12,0,0,1-17,0l-80-80a12,12,0,0,1,17-17L128,159l71.51-71.52a12,12,0,0,1,17,17Z"></path></svg> \ No newline at end of file
diff --git a/.config/awesome/assets/phosphor/caret-up-bold.svg b/.config/awesome/assets/phosphor/caret-up-bold.svg
new file mode 100644
index 0000000..eae0ae7
--- /dev/null
+++ b/.config/awesome/assets/phosphor/caret-up-bold.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M216.49,168.49a12,12,0,0,1-17,0L128,97,56.49,168.49a12,12,0,0,1-17-17l80-80a12,12,0,0,1,17,0l80,80A12,12,0,0,1,216.49,168.49Z"></path></svg> \ No newline at end of file
diff --git a/.config/awesome/assets/phosphor/init.lua b/.config/awesome/assets/phosphor/init.lua
index 421ceb2..2f0f6fc 100644
--- a/.config/awesome/assets/phosphor/init.lua
+++ b/.config/awesome/assets/phosphor/init.lua
@@ -1,14 +1,6 @@
local gfs = require "gears.filesystem"
-local qfs = require "quarrel.fs"
+local qiconset = require "quarrel.iconset"
----@type table<string, string>
-local icons = {}
local phosphor_dir = gfs.get_configuration_dir() .. "assets/phosphor/"
-for _, icon in ipairs(qfs.ls_files(phosphor_dir)) do
- if icon:match ".+%.(.+)" == "svg" then
- icons[icon:match("(.+)%..+"):gsub("-", "_")] = phosphor_dir .. icon
- end
-end
-
-return icons
+return qiconset(phosphor_dir)
diff --git a/.config/awesome/assets/phosphor/speaker-hifi-fill.svg b/.config/awesome/assets/phosphor/speaker-hifi-fill.svg
new file mode 100644
index 0000000..dce7932
--- /dev/null
+++ b/.config/awesome/assets/phosphor/speaker-hifi-fill.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M152,160a24,24,0,1,1-24-24A24,24,0,0,1,152,160ZM208,40V216a16,16,0,0,1-16,16H64a16,16,0,0,1-16-16V40A16,16,0,0,1,64,24H192A16,16,0,0,1,208,40ZM116,68a12,12,0,1,0,12-12A12,12,0,0,0,116,68Zm52,92a40,40,0,1,0-40,40A40,40,0,0,0,168,160Z"></path></svg> \ No newline at end of file
diff --git a/.config/awesome/assets/phosphor/timer-fill.svg b/.config/awesome/assets/phosphor/timer-fill.svg
new file mode 100644
index 0000000..530646d
--- /dev/null
+++ b/.config/awesome/assets/phosphor/timer-fill.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M128,40a96,96,0,1,0,96,96A96.11,96.11,0,0,0,128,40Zm45.66,61.66-40,40a8,8,0,0,1-11.32-11.32l40-40a8,8,0,0,1,11.32,11.32ZM96,16a8,8,0,0,1,8-8h48a8,8,0,0,1,0,16H104A8,8,0,0,1,96,16Z"></path></svg> \ No newline at end of file
diff --git a/.config/awesome/assets/phosphor/waveform-fill.svg b/.config/awesome/assets/phosphor/waveform-fill.svg
new file mode 100644
index 0000000..dd4d8ab
--- /dev/null
+++ b/.config/awesome/assets/phosphor/waveform-fill.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M216,40H40A16,16,0,0,0,24,56V200a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A16,16,0,0,0,216,40ZM72,152a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Zm32,32a8,8,0,0,1-16,0V72a8,8,0,0,1,16,0Zm32-16a8,8,0,0,1-16,0V88a8,8,0,0,1,16,0Zm32-16a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Zm32,8a8,8,0,0,1-16,0V96a8,8,0,0,1,16,0Z"></path></svg> \ No newline at end of file
diff --git a/.config/awesome/assets/simpleicons/firefox.svg b/.config/awesome/assets/simpleicons/firefox.svg
new file mode 100644
index 0000000..3cbc89c
--- /dev/null
+++ b/.config/awesome/assets/simpleicons/firefox.svg
@@ -0,0 +1 @@
+<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Firefox Browser</title><path d="M8.824 7.287c.008 0 .004 0 0 0zm-2.8-1.4c.006 0 .003 0 0 0zm16.754 2.161c-.505-1.215-1.53-2.528-2.333-2.943.654 1.283 1.033 2.57 1.177 3.53l.002.02c-1.314-3.278-3.544-4.6-5.366-7.477-.091-.147-.184-.292-.273-.446a3.545 3.545 0 01-.13-.24 2.118 2.118 0 01-.172-.46.03.03 0 00-.027-.03.038.038 0 00-.021 0l-.006.001a.037.037 0 00-.01.005L15.624 0c-2.585 1.515-3.657 4.168-3.932 5.856a6.197 6.197 0 00-2.305.587.297.297 0 00-.147.37c.057.162.24.24.396.17a5.622 5.622 0 012.008-.523l.067-.005a5.847 5.847 0 011.957.222l.095.03a5.816 5.816 0 01.616.228c.08.036.16.073.238.112l.107.055a5.835 5.835 0 01.368.211 5.953 5.953 0 012.034 2.104c-.62-.437-1.733-.868-2.803-.681 4.183 2.09 3.06 9.292-2.737 9.02a5.164 5.164 0 01-1.513-.292 4.42 4.42 0 01-.538-.232c-1.42-.735-2.593-2.121-2.74-3.806 0 0 .537-2 3.845-2 .357 0 1.38-.998 1.398-1.287-.005-.095-2.029-.9-2.817-1.677-.422-.416-.622-.616-.8-.767a3.47 3.47 0 00-.301-.227 5.388 5.388 0 01-.032-2.842c-1.195.544-2.124 1.403-2.8 2.163h-.006c-.46-.584-.428-2.51-.402-2.913-.006-.025-.343.176-.389.206-.406.29-.787.616-1.136.974-.397.403-.76.839-1.085 1.303a9.816 9.816 0 00-1.562 3.52c-.003.013-.11.487-.19 1.073-.013.09-.026.181-.037.272a7.8 7.8 0 00-.069.667l-.002.034-.023.387-.001.06C.386 18.795 5.593 24 12.016 24c5.752 0 10.527-4.176 11.463-9.661.02-.149.035-.298.052-.448.232-1.994-.025-4.09-.753-5.844z"/></svg> \ No newline at end of file
diff --git a/.config/awesome/assets/simpleicons/init.lua b/.config/awesome/assets/simpleicons/init.lua
new file mode 100644
index 0000000..4aaa2d3
--- /dev/null
+++ b/.config/awesome/assets/simpleicons/init.lua
@@ -0,0 +1,6 @@
+local gfs = require "gears.filesystem"
+local qiconset = require "quarrel.iconset"
+
+local simpleicons_dir = gfs.get_configuration_dir() .. "assets/simpleicons/"
+
+return qiconset(simpleicons_dir)
diff --git a/.config/awesome/assets/simpleicons/librewolf.svg b/.config/awesome/assets/simpleicons/librewolf.svg
new file mode 100644
index 0000000..a6dab52
--- /dev/null
+++ b/.config/awesome/assets/simpleicons/librewolf.svg
@@ -0,0 +1 @@
+<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>LibreWolf</title><path d="M12 0C5.3726 0 0 5.3726 0 12s5.3726 12 12 12 12-5.3726 12-12A12 12 0 0 0 12 0m.0306 2.4174c2.539 0 4.7927.9097 6.619 2.6718.8799.849 1.47 1.654 1.9752 2.6953.6834 1.4085.96 2.6337.96 4.2503 0 2.1282-.6144 3.9841-1.891 5.7107-.3792.513-1.1202 1.2925-1.612 1.6955-1.3487 1.1057-3.068 1.8537-4.7739 2.0774-.5894.0772-1.982.0868-2.5232.0174v.0001c-1.681-.2156-3.2887-.8859-4.6919-1.9555-.4576-.3489-1.3163-1.213-1.6911-1.702-1.0497-1.3694-1.7025-2.9237-1.9179-4.5663-.0922-.7032-.0916-1.996.0011-2.6387.4635-3.213 2.4855-5.9688 5.389-7.3449 1.3505-.64 2.5868-.9111 4.1567-.9111m-.0315 1.4514c-1.4755-.0014-2.7693.3367-4.0497 1.0586-1.1657.6572-2.3708 1.8637-3.0308 3.0342-.486.862-.8417 1.8852-.9806 2.82-.091.613-.0929 1.8301-.0036 2.417.1786 1.1746.6241 2.3354 1.27 3.3091.3527.5317.3416.5259.6528.3479.5566-.3184.7018-.4726 1.005-1.0666.3546-.6946.7336-1.3016 1.0762-1.7232.3953-.4867.4496-.5732.648-1.0327.0997-.2312.2788-.5708.3978-.7546l.2165-.3341-.28-.2753c-.372-.3655-.6814-.7802-.7395-.991-.0595-.2152.0108-.3353.2902-.4967.2825-.1632.4845-.21 1.2396-.2877.3677-.0377.7668-.1069.8925-.1547.125-.0475.4988-.251.8305-.452.9577-.5807 1.0388-.6107 1.613-.5983.485.0106.4859.0103.7734-.1635.6026-.3642 1.4681-1.0215 2.607-1.9797.2183-.1838.4286-.328.4674-.3206.161.031.457.757.5019 1.2315.0272.2878-.0285.5725-.1992 1.018-.0466.1215-.068.2375-.0477.2577.0489.0486.2165-.1607.2165-.2703 0-.1397.0924-.1557.2182-.0377.0915.0859.1705.1105.3545.1105.201 0 .2507.0186.329.1232.1332.1775.1644.6616.0625.9682-.099.2983-.3986.6423-.6704.77-.2431.1143-.7354.5951-.9658.9432-.0882.1332-.2536.411-.3675.6172-.114.2063-.305.518-.4246.6925-.2354.3436-.437.823-.6316 1.5017-.1143.3987-.1238.5025-.1285 1.3918-.0057 1.0742-.0613 1.3378-.3518 1.6686-.3301.376-.6596 1.2018-.6873 1.7225-.0175.3286-.0682.649-.1401.8862-.0317.1045.0275.1032.38-.0079 1.027-.3237 1.8638-.7588 2.6718-1.3889.4296-.335 1.1722-1.0847 1.4835-1.4978.8445-1.1202 1.419-2.5235 1.5881-3.8785.0814-.6526.0342-2.0332-.0901-2.6325-.3293-1.5875-1.039-2.9284-2.1566-4.0759-.6968-.7154-1.3311-1.187-2.1473-1.5961-1.2384-.621-2.2994-.8718-3.6932-.8733m1.516 5.314-.6964.0976-.433.5544.6966-.0978z"/></svg> \ No newline at end of file
diff --git a/.config/awesome/assets/simpleicons/spotify.svg b/.config/awesome/assets/simpleicons/spotify.svg
new file mode 100644
index 0000000..8d4d095
--- /dev/null
+++ b/.config/awesome/assets/simpleicons/spotify.svg
@@ -0,0 +1 @@
+<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Spotify</title><path d="M12 0C5.4 0 0 5.4 0 12s5.4 12 12 12 12-5.4 12-12S18.66 0 12 0zm5.521 17.34c-.24.359-.66.48-1.021.24-2.82-1.74-6.36-2.101-10.561-1.141-.418.122-.779-.179-.899-.539-.12-.421.18-.78.54-.9 4.56-1.021 8.52-.6 11.64 1.32.42.18.479.659.301 1.02zm1.44-3.3c-.301.42-.841.6-1.262.3-3.239-1.98-8.159-2.58-11.939-1.38-.479.12-1.02-.12-1.14-.6-.12-.48.12-1.021.6-1.141C9.6 9.9 15 10.561 18.72 12.84c.361.181.54.78.241 1.2zm.12-3.36C15.24 8.4 8.82 8.16 5.16 9.301c-.6.179-1.2-.181-1.38-.721-.18-.601.18-1.2.72-1.381 4.26-1.26 11.28-1.02 15.721 1.621.539.3.719 1.02.419 1.56-.299.421-1.02.599-1.559.3z"/></svg> \ No newline at end of file
diff --git a/.config/awesome/curious.lua b/.config/awesome/curious.lua
index 3a0bf5d..66c33da 100644
--- a/.config/awesome/curious.lua
+++ b/.config/awesome/curious.lua
@@ -15,11 +15,11 @@ local interface = Gio.DBusInterfaceInfo {
out_args = {
Gio.DBusArgInfo {
name = "widget_tree",
- signature = "av"
- }
- }
- }
- }
+ signature = "av",
+ },
+ },
+ },
+ },
}
-- iterate with ipairs, get the length of the array, check if key value from pairs is a number lower than the length and if so, use as a dict key cause it's not gonna work with :add anyway
@@ -43,17 +43,17 @@ local function serialize(builder, t)
signature = "b"
data = v
elseif _type == "thread" or _type == "function" or (_type == "userdata" and not getmetatable(v).__tostring) then
- local address = tonumber(tostring(v):match("%a+: 0x(%w+)"), 16)
+ local address = tonumber(tostring(v):match "%a+: 0x(%w+)", 16)
signature = "(s(t))"
data = { _type, address }
elseif _type == "userdata" then
if getmetatable(v).emit_signal then -- One of the properties available on any awesome luaobject
- local _, _, content = tostring(v):match("%a+/(.*)")
+ local _, _, content = tostring(v):match "%a+/(.*)"
local stringified = tostring(v)
if content then
stringified = content
end
- local type, extra, address = stringified:match("(%a+)(.*): 0x(%w+)")
+ local type, extra, address = stringified:match "(%a+)(.*): 0x(%w+)"
if extra ~= "" then
extra:sub(2, -2)
signature = "(s(st))"
@@ -64,7 +64,7 @@ local function serialize(builder, t)
end
else
signature = "(s())"
- data = { _type, {}}
+ data = { _type, {} }
end
end
@@ -72,16 +72,16 @@ local function serialize(builder, t)
builder:add("v", GLib.Variant(signature, data))
else
print(type(signature), type(data))
- local variant = GLib.Variant("{sd}", { "test", 4})
+ local variant = GLib.Variant("{sd}", { "test", 4 })
builder:add_value(GLib.Variant("v", variant))
end
end
end
function methods.Select(parameters, invocation)
- local builder = GLib.VariantBuilder(GLib.VariantType("av"))
+ local builder = GLib.VariantBuilder(GLib.VariantType "av")
serialize(builder, { "test", some_key = "some_value" })
- local tuple_builder = GLib.VariantBuilder(GLib.VariantType("(av)"))
+ local tuple_builder = GLib.VariantBuilder(GLib.VariantType "(av)")
tuple_builder:add_value(builder:_end())
-- builder
-- invocation:return_value(GLib.Variant("(av)", { builder }))
@@ -89,7 +89,10 @@ function methods.Select(parameters, invocation)
end
local function handle_call(_, _, _, _, method, parameters, invocation)
- if not methods[method] then print("invalid"); return end
+ if not methods[method] then
+ print "invalid"
+ return
+ end
gpcall(methods[method], parameters, invocation)
end
@@ -97,4 +100,9 @@ local function acquire(connection)
connection:register_object("/com/twoexem/delta/Curious", interface, GObject.Closure(handle_call))
end
-local connection = Gio.bus_own_name(Gio.BusType.SESSION, "com.twoexem.delta.Curious", Gio.BusNameOwnerFlags.NONE, GObject.Closure(acquire))
+local connection = Gio.bus_own_name(
+ Gio.BusType.SESSION,
+ "com.twoexem.delta.Curious",
+ Gio.BusNameOwnerFlags.NONE,
+ GObject.Closure(acquire)
+)
diff --git a/.config/awesome/lib/lit b/.config/awesome/lib/lit
new file mode 160000
+Subproject 06b6581137c04520cc4ffccb8f1103b961a187f
diff --git a/.config/awesome/misc/autostart.lua b/.config/awesome/misc/autostart.lua
index b089068..291e39d 100644
--- a/.config/awesome/misc/autostart.lua
+++ b/.config/awesome/misc/autostart.lua
@@ -1,7 +1,7 @@
local awful = require "awful"
-local quarrel = require "quarrel"
+local qpersistent = require "quarrel.persistent"
-if quarrel.is_restart() then
+if qpersistent.is_restart() then
return
end
@@ -11,9 +11,10 @@ local programs = {
"clipcatd",
"wezterm",
"wezterm start --class code_term",
- "firefox",
- "discord",
- "LD_PRELOAD=/usr/lib/spotify-adblock.so spotify",
+ "librewolf",
+ "keepassxc",
+ "fractal",
+ -- "LD_PRELOAD=/usr/lib/spotify-adblock.so spotify",
}
for _, program in ipairs(programs) do
diff --git a/.config/awesome/misc/cfg.lua b/.config/awesome/misc/cfg.lua
index aceaa08..f62612f 100644
--- a/.config/awesome/misc/cfg.lua
+++ b/.config/awesome/misc/cfg.lua
@@ -3,21 +3,26 @@ local awful = require "awful"
local cfg = {
terminal = "wezterm",
tags = {
+ -- home
{
layout = awful.layout.suit.floating,
selected = true,
},
+ -- browser
{
+ -- layout = awful.layout.suit.fair,
layout = awful.layout.suit.floating,
},
+ -- messaging
{
layout = awful.layout.suit.tile.left,
master_width_factor = 0.7,
},
- {
- layout = awful.layout.suit.tile.top,
- master_width_factor = 0.2,
- },
+
+ -- {
+ -- layout = awful.layout.suit.tile.top,
+ -- master_width_factor = 0.2,
+ -- },
{
layout = awful.layout.suit.tile.right,
-- master_width_factor = 0.7,
diff --git a/.config/awesome/misc/keys.lua b/.config/awesome/misc/keys.lua
index 9d994ce..8d703b8 100644
--- a/.config/awesome/misc/keys.lua
+++ b/.config/awesome/misc/keys.lua
@@ -3,21 +3,22 @@ local backlight = require "services.backlight"
local beautiful = require "beautiful"
local cfg = require "misc.cfg"
local fresnel = require "ui.fresnel"
+local gtable = require "gears.table"
local gtimer = require "gears.timer"
local insightful = require "ui.insightful"
local naughty = require "naughty"
+-- local mpris = (require "services.mpris")
+local mpris = require "ui.statusbar.panel.widgets.mpris"
local playerctl = require "services.playerctl"
local powermenu = require "ui.powermenu"
local qbind = require "quarrel.bind"
-local qstore = require "quarrel.store"
-local qvars = require "quarrel.vars"
local recording = { false, "" }
client.connect_signal("request::default_mousebindings", function()
awful.mouse.append_client_mousebindings {
- qbind:new {
- triggers = qvars.btns.left,
+ qbind {
+ triggers = qbind.btns.left,
press = function(c)
c:activate {
context = "mouse_click",
@@ -26,9 +27,9 @@ client.connect_signal("request::default_mousebindings", function()
group = "client",
desc = "raise client",
},
- qbind:new {
- mods = qvars.mods.M,
- triggers = qvars.btns.left,
+ qbind {
+ mods = qbind.mods.M,
+ triggers = qbind.btns.left,
press = function(c)
c:activate {
context = "mouse_click",
@@ -38,9 +39,9 @@ client.connect_signal("request::default_mousebindings", function()
group = "client",
desc = "move client",
},
- qbind:new {
- mods = qvars.mods.M,
- triggers = qvars.btns.right,
+ qbind {
+ mods = qbind.mods.M,
+ triggers = qbind.btns.right,
press = function(c)
c:activate {
context = "mouse_click",
@@ -55,8 +56,8 @@ end)
client.connect_signal("request::default_keybindings", function()
awful.keyboard.append_client_keybindings {
- qbind:new {
- mods = qvars.mods.MC,
+ qbind {
+ mods = qbind.mods.MC,
triggers = "q",
press = function(c)
c:kill()
@@ -64,8 +65,8 @@ client.connect_signal("request::default_keybindings", function()
group = "client",
desc = "close",
},
- qbind:new {
- mods = qvars.mods.M,
+ qbind {
+ mods = qbind.mods.M,
triggers = "m",
press = function(c)
c.maximized = not c.maximized
@@ -73,8 +74,8 @@ client.connect_signal("request::default_keybindings", function()
group = "client",
desc = "(un)maximize",
},
- qbind:new {
- mods = qvars.mods.M,
+ qbind {
+ mods = qbind.mods.M,
triggers = "n",
press = function(c)
gtimer.delayed_call(function()
@@ -84,8 +85,8 @@ client.connect_signal("request::default_keybindings", function()
group = "client",
desc = "minimize",
},
- qbind:new {
- mods = qvars.mods.M,
+ qbind {
+ mods = qbind.mods.M,
triggers = "f",
press = function(c)
c.fullscreen = not c.fullscreen
@@ -97,15 +98,15 @@ client.connect_signal("request::default_keybindings", function()
end)
awful.keyboard.append_global_keybindings {
- qbind:new {
- mods = qvars.mods.MC,
+ qbind {
+ mods = qbind.mods.MC,
triggers = "r",
press = awesome.restart,
group = "awesome",
desc = "restart awesome",
},
- qbind:new {
- mods = qvars.mods.M,
+ qbind {
+ mods = qbind.mods.M,
triggers = "F1",
press = function()
insightful:toggle()
@@ -113,8 +114,8 @@ awful.keyboard.append_global_keybindings {
group = "awesome",
desc = "toggle insightful",
},
- qbind:new {
- mods = qvars.mods.M,
+ qbind {
+ mods = qbind.mods.M,
triggers = "space",
press = function()
fresnel:show()
@@ -122,17 +123,17 @@ awful.keyboard.append_global_keybindings {
group = "awesome",
desc = "toggle fresnel",
},
- qbind:new {
- mods = qvars.mods.M,
+ qbind {
+ mods = qbind.mods.M,
triggers = "p",
press = function()
- qstore.panel_toggle:press()
+ awful.screen.focused().bar:toggle()
end,
group = "awesome",
desc = "toggle bar panel",
},
- qbind:new {
- mods = qvars.mods.M,
+ qbind {
+ mods = qbind.mods.M,
triggers = "l",
press = function()
powermenu:toggle()
@@ -141,7 +142,7 @@ awful.keyboard.append_global_keybindings {
desc = "toggle powermenu",
},
- qbind:new {
+ qbind {
triggers = "XF86AudioMute",
press = function()
awful.spawn "wpctl set-mute @DEFAULT_SINK@ toggle"
@@ -149,46 +150,65 @@ awful.keyboard.append_global_keybindings {
group = "audio",
desc = "mute",
},
- qbind:new {
+ qbind {
triggers = {
{ "XF86AudioRaiseVolume", true },
{ "XF86AudioLowerVolume", false },
},
press = function(up)
if up then
- awful.spawn "wpctl set-volume @DEFAULT_SINK@ 5%+"
+ -- awful.spawn "wpctl set-volume @DEFAULT_SINK@ 5%+"
+ print "up"
else
- awful.spawn "wpctl set-volume @DEFAULT_SINK@ 5%-"
+ print "down"
+ -- awful.spawn "wpctl set-volume @DEFAULT_SINK@ 5%-"
end
end,
group = "audio",
desc = "increase/decrease volume",
},
- qbind:new {
+ qbind {
triggers = {
{ "XF86AudioNext", true },
{ "XF86AudioPrev", false },
},
press = function(next)
+ local active_player = playerctl:list()[mpris.active_player_index].instance
if next then
- playerctl:next()
+ playerctl:next(active_player)
else
- playerctl:previous()
+ playerctl:previous(active_player)
end
end,
group = "audio",
desc = "previous/next song",
},
- qbind:new {
+ qbind {
triggers = "XF86AudioPlay",
press = function()
- playerctl:play_pause()
+ playerctl:play_pause(playerctl:list()[mpris.active_player_index].instance)
end,
group = "audio",
desc = "(un)pause song",
},
+ qbind {
+ mods = qbind.mods.M,
+ triggers = {
+ { "Next", true },
+ { "Prior", false },
+ },
+ press = function(next)
+ if next then
+ mpris.next_player()
+ else
+ mpris.previous_player()
+ end
+ end,
+ group = "audio",
+ desc = "previous/next player",
+ },
- qbind:new {
+ qbind {
triggers = {
{ "XF86MonBrightnessUp", true },
{ "XF86MonBrightnessDown", false },
@@ -204,8 +224,8 @@ awful.keyboard.append_global_keybindings {
desc = "increase/decrease brightness",
},
- qbind:new {
- mods = qvars.mods.M,
+ qbind {
+ mods = qbind.mods.M,
triggers = "Return",
press = function()
awful.spawn(cfg.terminal)
@@ -214,7 +234,7 @@ awful.keyboard.append_global_keybindings {
desc = "launch terminal",
},
- qbind:new {
+ qbind {
triggers = "Print",
press = function()
local date = os.date "%Y%m%d_%H%M%S"
@@ -235,8 +255,8 @@ awful.keyboard.append_global_keybindings {
group = "screenshot",
desc = "take fullscreen screenshot",
},
- qbind:new {
- mods = qvars.mods.S,
+ qbind {
+ mods = qbind.mods.S,
triggers = "Print",
press = function()
local date = os.date "%Y%m%d_%H%M%S"
@@ -256,8 +276,8 @@ awful.keyboard.append_global_keybindings {
desc = "take region screenshot",
},
- qbind:new {
- mods = qvars.mods.M,
+ qbind {
+ mods = qbind.mods.M,
triggers = "Print",
press = function()
if recording[1] then
@@ -283,8 +303,8 @@ awful.keyboard.append_global_keybindings {
desc = "toggle recording",
},
- qbind:new {
- mods = qvars.mods.M,
+ qbind {
+ mods = qbind.mods.M,
triggers = "k",
press = function()
awful.spawn "xkblayout-state set +1"
@@ -292,8 +312,8 @@ awful.keyboard.append_global_keybindings {
group = "keyboard",
desc = "next keyboard layout",
},
- qbind:new {
- mods = qvars.mods.MS,
+ qbind {
+ mods = qbind.mods.MS,
triggers = "k",
press = function()
awful.spawn "xkblayout-state set -1"
@@ -302,22 +322,35 @@ awful.keyboard.append_global_keybindings {
desc = "previous keyboard layout",
},
- qbind:new {
- mods = qvars.mods.M,
+ qbind {
+ mods = qbind.mods.M,
triggers = "Up",
press = awful.tag.viewprev,
group = "tag",
desc = "switch to previous",
},
- qbind:new {
- mods = qvars.mods.M,
+ qbind {
+ mods = qbind.mods.M,
triggers = "Down",
press = awful.tag.viewnext,
group = "tag",
desc = "switch to next",
},
- qbind:new {
- mods = qvars.mods.MC,
+ qbind {
+ mods = qbind.mods.M,
+ triggers = gtable.join(awful.key.keygroups.numrow, awful.key.keygroups.numpad),
+ press = function(idx)
+ local tag = awful.screen.focused().tags[idx]
+ if not tag then
+ return
+ end
+ tag:view_only()
+ end,
+ group = "tag",
+ desc = "switch to the specified tag (if it exists)",
+ },
+ qbind {
+ mods = qbind.mods.MC,
triggers = "x",
press = function()
local tag = awful.screen.focused().selected_tag
@@ -326,4 +359,16 @@ awful.keyboard.append_global_keybindings {
group = "tag",
desc = "reset master width",
},
+ qbind {
+ mods = qbind.mods.M,
+ triggers = {
+ { "XF86AudioRaiseVolume", true }, -- volume roller produces these events
+ { "XF86AudioLowerVolume", false },
+ },
+ press = function(up)
+ awful.spawn("xdotool click " .. (up and "4" or "5"))
+ end,
+ group = "misc",
+ desc = "scroll up/down",
+ },
}
diff --git a/.config/awesome/misc/rules.lua b/.config/awesome/misc/rules.lua
index 92ac596..18b11f1 100644
--- a/.config/awesome/misc/rules.lua
+++ b/.config/awesome/misc/rules.lua
@@ -20,6 +20,17 @@ ruled.client.connect_signal("request::rules", function()
},
{
+ id = "unlock_db",
+ rule = {
+ name = "Unlock Database - KeePassXC",
+ },
+ properties = {
+ sticky = true,
+ floating = true,
+ },
+ },
+
+ {
id = "titlebars",
rule_any = {
type = {
@@ -56,7 +67,7 @@ ruled.client.connect_signal("request::rules", function()
{
id = "chat_tag",
rule_any = {
- class = { "discord" },
+ class = { "fractal" },
},
properties = {
screen = 1,
@@ -64,17 +75,17 @@ ruled.client.connect_signal("request::rules", function()
},
},
- {
- id = "music_tag",
- rule_any = {
- class = { "Spotify" },
- },
- properties = {
- screen = 1,
- tag = awful.screen.focused().tags[4],
- titlebars_enabled = false,
- },
- },
+ -- {
+ -- id = "music_tag",
+ -- rule_any = {
+ -- class = { "Spotify" },
+ -- },
+ -- properties = {
+ -- screen = 1,
+ -- tag = awful.screen.focused().tags[4],
+ -- titlebars_enabled = false,
+ -- },
+ -- },
{
id = "code_tag",
@@ -83,7 +94,7 @@ ruled.client.connect_signal("request::rules", function()
},
properties = {
screen = 1,
- tag = awful.screen.focused().tags[5],
+ tag = awful.screen.focused().tags[4],
},
},
@@ -94,7 +105,7 @@ ruled.client.connect_signal("request::rules", function()
},
properties = {
screen = 1,
- tag = awful.screen.focused().tags[6],
+ tag = awful.screen.focused().tags[5],
},
},
}
diff --git a/.config/awesome/prismite.lua b/.config/awesome/prismite.lua
index 5de089f..497a1b9 100644
--- a/.config/awesome/prismite.lua
+++ b/.config/awesome/prismite.lua
@@ -1,39 +1,42 @@
local naughty = require "naughty"
-local qvars = require "quarrel.vars"
+local qcolor = require "quarrel.color"
+local qui = require "quarrel.ui"
local xresources = require "beautiful.xresources"
local dpi = xresources.apply_dpi
local theme = {}
-theme.font = qvars.font
+theme.font = qui.font()
-theme.bg_normal = qvars.colors.bg
-theme.bg_focus = qvars.colors.bg
-theme.bg_urgent = qvars.colors.bg
-theme.bg_minimize = qvars.colors.bg
-theme.bg_systray = qvars.colors.bg
+theme.bg_normal = qcolor.palette.bg()
+theme.bg_focus = qcolor.palette.bg()
+theme.bg_urgent = qcolor.palette.bg()
+theme.bg_minimize = qcolor.palette.bg()
+theme.bg_systray = qcolor.palette.bg()
-theme.fg_normal = qvars.colors.fg
-theme.fg_focus = qvars.colors.fg
-theme.fg_urgent = qvars.colors.fg
-theme.fg_minimize = qvars.colors.fg
+theme.fg_normal = qcolor.palette.fg()
+theme.fg_focus = qcolor.palette.fg()
+theme.fg_urgent = qcolor.palette.fg()
+theme.fg_minimize = qcolor.palette.fg()
-theme.useless_gap = dpi(2)
-theme.border_width = qvars.border_width
-theme.border_normal = qvars.colors.bright.black
-theme.border_focus = qvars.colors.bright.black
-theme.border_marked = qvars.colors.bright.black
+-- theme.useless_gap = dpi(2)
+theme.useless_gap = qui.PADDING / 2
+theme.border_width = qui.BORDER_WIDTH
+theme.border_normal = qcolor.palette.border()
+theme.border_focus = qcolor.palette.border()
+theme.border_marked = qcolor.palette.border()
theme.notification_icon_size = dpi(32)
-theme.notification_border_width = qvars.border_width
+theme.notification_border_width = qui.BORDER_WIDTH
theme.notification_border_color = theme.border_normal
-theme.notification_max_width = qvars.char_width * 48
-theme.notification_shape = qvars.shape
+theme.notification_max_width = qui.CHAR_WIDTH * 48
+-- theme.notification_max_width = qui.CHAR_WIDTH * 48 * 4
+theme.notification_shape = qui.shape
theme.notification_spacing = theme.useless_gap * 2
-naughty.config.defaults.timeout = qvars.notif_timeout
+-- naughty.config.defaults.timeout = qvars.notif_timeout
naughty.config.defaults.position = "bottom_right"
-naughty.config.defaults.border_width = qvars.border_width
+-- naughty.config.defaults.border_width = qui.BORDER_WIDTH
theme.tasklist_plain_task_name = true
theme.enable_spawn_cursor = false
diff --git a/.config/awesome/quarrel/animation/bezier.lua b/.config/awesome/quarrel/animation/bezier.lua
new file mode 100644
index 0000000..a505e48
--- /dev/null
+++ b/.config/awesome/quarrel/animation/bezier.lua
@@ -0,0 +1,89 @@
+local gtable = require "gears.table"
+
+-- port of https://github.com/WebKit/WebKit/blob/da934454c84ac2dcbf9fca9e5f4ac2644ef25d72/Source/WebCore/platform/graphics/UnitBezier.h
+
+local bezier = {}
+
+function bezier:sample_x(t)
+ -- `ax t^3 + bx t^2 + cx t' expanded using Horner's rule.
+ return ((self.ax * t + self.bx) * t + self.cx) * t
+end
+
+function bezier:sample_y(t)
+ return ((self.ay * t + self.by) * t + self.cy) * t
+end
+
+function bezier:sample_derivative_x(t)
+ return (3.0 * self.ax * t + 2.0 * self.bx) * t + self.cx
+end
+
+function bezier:solve_x(x, epsilon)
+ local x2, d2
+ local t2 = x
+
+ -- First try a few iterations of Newton's method -- normally very fast.
+ for _ = 1, 8 do
+ x2 = self:sample_x(t2) - x
+ if math.abs(x2) < epsilon then
+ return t2
+ end
+ d2 = self:sample_derivative_x(t2)
+ if math.abs(d2) < 1e-6 then
+ break
+ end
+ t2 = t2 - x2 / d2
+ end
+
+ -- Fall back to the bisection method for reliability.
+ local t0 = 0
+ local t1 = 1
+ t2 = x
+
+ if t2 < t0 then
+ return t0
+ end
+ if t2 > t1 then
+ return t1
+ end
+
+ while t0 < t1 do
+ x2 = self:sample_x(t2)
+ if math.abs(x2 - x) < epsilon then
+ return t2
+ end
+ if x > x2 then
+ t0 = t2
+ else
+ t1 = t2
+ end
+ t2 = (t1 - t0) * 0.5 + t0
+ end
+
+ -- Failure.
+ return t2
+end
+
+function bezier:solve(x, epsilon)
+ return self:sample_y(self:solve_x(x, epsilon))
+end
+
+local function new(x1, y1, x2, y2)
+ local obj = gtable.crush({}, bezier)
+
+ -- Calculate the polynomial coefficients, implicit first and last control points are (0,0) and (1,1).
+ obj.cx = 3.0 * x1
+ obj.bx = 3.0 * (x2 - x1) - obj.cx
+ obj.ax = 1.0 - obj.cx - obj.bx
+
+ obj.cy = 3.0 * y1
+ obj.by = 3.0 * (y2 - y1) - obj.cy
+ obj.ay = 1.0 - obj.cy - obj.by
+
+ return obj
+end
+
+return setmetatable(bezier, {
+ __call = function(_, ...)
+ return new(...)
+ end,
+})
diff --git a/.config/awesome/quarrel/animation/init.lua b/.config/awesome/quarrel/animation/init.lua
new file mode 100644
index 0000000..c57b01a
--- /dev/null
+++ b/.config/awesome/quarrel/animation/init.lua
@@ -0,0 +1,249 @@
+-------------------------------------------
+-- @author https://github.com/Kasper24
+-- @copyright 2021-2025 Kasper24
+-------------------------------------------
+local GLib = require("lgi").GLib
+local gobject = require "gears.object"
+local gpcall = require "gears.protected_call"
+local gtable = require "gears.table"
+local gtimer = require "gears.timer"
+local subscribable = require "quarrel.animation.subscribable"
+-- local qconsts = require "quarrel.consts"
+local qtween = require "quarrel.animation.tween"
+-- local qbezier = require "quarrel.animation.bezier"
+local ipairs = ipairs
+local table = table
+local pairs = pairs
+
+local animation_manager = {}
+
+-- local easing_bezier = qbezier(0.2, 0, 0, 1)
+-- animation_manager.consts = qconsts.protect(function()
+-- local duration = 0.3
+-- return {
+-- DURATION = duration,
+-- INTRO = duration / 4,
+-- EASING = function(t, b, c, d)
+-- local epsilon = 1000 / d
+-- return c * easing_bezier:solve(t/d, epsilon) + b
+-- end
+-- }
+-- end)
+
+local animation = {}
+
+local function second_to_micro(sec)
+ return sec * 1000000
+end
+
+local function framerate_tomilli(framerate)
+ return 1000 / framerate
+end
+
+local function on_no_running_animations(self, callback)
+ gtimer.start_new(0.1, function()
+ if #self._private.animations <= 0 then
+ callback()
+ return false
+ else
+ local has_non_looped_anim = false
+ for _, animation in ipairs(self._private.animations) do
+ if animation.loop == false then
+ has_non_looped_anim = true
+ end
+ end
+
+ if has_non_looped_anim == false then
+ callback()
+ return false
+ end
+ end
+
+ return true
+ end)
+end
+
+local function animation_loop(self)
+ self._private.source_id = GLib.timeout_add(
+ GLib.PRIORITY_DEFAULT,
+ framerate_tomilli(self._private.framerate),
+ function()
+ for index, animation in ipairs(self._private.animations) do
+ if animation.state == true then
+ -- compute delta time
+ local time = GLib.get_monotonic_time()
+ local delta = time - animation.last_elapsed
+ animation.last_elapsed = time
+
+ -- If pos is true, the animation has ended
+ local pos = gpcall(animation.tween.update, animation.tween, delta)
+ if pos == true then
+ -- Loop the animation, don't end it.
+ -- Useful for widgets like the spinning cicle
+ if animation.loop == true then
+ animation.tween:reset()
+ else
+ animation.state = false
+
+ -- Snap to end
+ animation.pos = animation.tween.target
+
+ gpcall(animation.emit_signal, animation, "update", animation.pos)
+ gpcall(animation.fire, animation, animation.pos)
+
+ gpcall(animation.emit_signal, animation, "ended", animation.pos)
+ gpcall(animation.ended.fire, animation, animation.pos)
+
+ table.remove(self._private.animations, index)
+ end
+ -- Animation in process, keep updating
+ else
+ animation.pos = pos
+
+ gpcall(animation.emit_signal, animation, "update", animation.pos)
+ gpcall(animation.fire, animation, animation.pos)
+ end
+ else
+ table.remove(self._private.animations, index)
+ end
+ end
+
+ -- call again the function after cooldown
+ return true
+ end
+ )
+end
+
+function animation:set(args)
+ args = args or {}
+
+ -- Awestoer/Rubbto compatibility
+ -- I'd rather this always be a table, but Awestore/Rubbto
+ -- except the :set() method to have 1 number value parameter
+ -- used to set the target
+ local is_table = type(args) == "table"
+ local initial = is_table and (args.pos or self.pos) or self.pos
+ local subject = is_table and (args.subject or self.subject) or self.subject
+ local target = is_table and (args.target or self.target) or args
+ local duration = is_table and (args.duration or self.duration) or self.duration
+ local easing = is_table and (args.easing or self.easing) or self.easing
+
+ if self.tween == nil or self.reset_on_stop == true then
+ self.tween = qtween.new {
+ initial = initial,
+ subject = subject,
+ target = target,
+ duration = second_to_micro(duration),
+ easing = easing,
+ }
+ end
+
+ if self._private.anim_manager._private.instant and self.override_instant ~= true then
+ self.pos = self.tween.target
+ self:fire(self.pos)
+ self:emit_signal("update", self.pos)
+
+ self.state = false
+ self.ended:fire(self.pos)
+ self:emit_signal("ended", self.pos)
+ return
+ end
+
+ if self._private.anim_manager._private.animations[self.index] == nil then
+ table.insert(self._private.anim_manager._private.animations, self)
+ end
+
+ self.state = true
+ self.last_elapsed = GLib.get_monotonic_time()
+
+ self.started:fire()
+ self:emit_signal "started"
+end
+
+-- Rubato compatibility
+function animation:abort()
+ self.state = false
+end
+
+function animation:stop()
+ self.state = false
+end
+
+function animation:initial()
+ return self._private.initial
+end
+
+function animation_manager:set_instant(value)
+ if value == true and self._private.instant == false then
+ on_no_running_animations(self, function()
+ -- GLib.source_remove(self._private.source_id)
+ self._private.instant = true
+ end)
+ elseif self._private.instant == true then
+ self._private.instant = false
+ -- animation_loop(self)
+ end
+end
+
+function animation_manager:set_framerate(value)
+ self._private.framerate = value
+ -- if self._private.instant == false then
+ on_no_running_animations(self, function()
+ GLib.source_remove(self._private.source_id)
+ animation_loop(self)
+ end)
+ -- end
+end
+
+function animation_manager:new(args)
+ args = args or {}
+
+ args.pos = args.pos or 0
+ args.subject = args.subject or nil
+ args.target = args.target or nil
+ args.duration = args.duration or 0
+ args.easing = args.easing or nil
+ args.loop = args.loop or false
+ args.signals = args.signals or {}
+ args.update = args.update or nil
+ args.reset_on_stop = args.reset_on_stop == nil and true or args.reset_on_stop
+
+ -- Awestoer/Rubbto compatibility
+ args.subscribed = args.subscribed or nil
+ local ret = subscribable()
+ ret.started = subscribable()
+ ret.ended = subscribable()
+ if args.subscribed ~= nil then
+ ret:subscribe(args.subscribed)
+ end
+
+ for sig, sigfun in pairs(args.signals) do
+ ret:connect_signal(sig, sigfun)
+ end
+ if args.update ~= nil then
+ ret:connect_signal("update", args.update)
+ end
+
+ gtable.crush(ret, args, true)
+ gtable.crush(ret, animation, true)
+
+ ret._private = {}
+ ret._private.anim_manager = self
+ ret._private.initial = args.pos
+ -- Can't have it private for rubato compatibility
+ ret.state = false
+
+ return ret
+end
+
+local instance = gobject {}
+gtable.crush(instance, animation_manager, true)
+
+instance._private = {}
+instance._private.animations = {}
+instance._private.instant = false
+instance._private.framerate = 60
+
+animation_loop(instance)
+
+return instance
diff --git a/.config/awesome/quarrel/animation/subscribable/init.lua b/.config/awesome/quarrel/animation/subscribable/init.lua
new file mode 100644
index 0000000..9542704
--- /dev/null
+++ b/.config/awesome/quarrel/animation/subscribable/init.lua
@@ -0,0 +1,47 @@
+-------------------------------------------
+-- @author https://github.com/Kasper24
+-- @copyright 2021-2025 Kasper24
+-- Adopted from rubato
+-------------------------------------------
+local gobject = require "gears.object"
+
+-- Kidna copying awesotre's stores on a surface level for added compatibility
+local function subscribable(args)
+ local ret = gobject {}
+ local subscribed = {}
+
+ -- Subscrubes a function to the object so that it's called when `fire` is
+ -- Calls subscribe_callback if it exists as well
+ function ret:subscribe(func)
+ local id = tostring(func):gsub("function: ", "")
+ subscribed[id] = func
+
+ if self.subscribe_callback then
+ self.subscribe_callback(func)
+ end
+ end
+
+ -- Unsubscribes a function and calls unsubscribe_callback if it exists
+ function ret:unsubscribe(func)
+ if not func then
+ subscribed = {}
+ else
+ local id = tostring(func):gsub("function: ", "")
+ subscribed[id] = nil
+ end
+
+ if self.unsubscribe_callback then
+ self.unsubscribe_callback(func)
+ end
+ end
+
+ function ret:fire(...)
+ for _, func in pairs(subscribed) do
+ func(...)
+ end
+ end
+
+ return ret
+end
+
+return subscribable
diff --git a/.config/awesome/quarrel/animation/tween/init.lua b/.config/awesome/quarrel/animation/tween/init.lua
new file mode 100644
index 0000000..5f4ce51
--- /dev/null
+++ b/.config/awesome/quarrel/animation/tween/init.lua
@@ -0,0 +1,554 @@
+-------------------------------------------
+-- @author https://github.com/Kasper24
+-- @copyright 2021-2025 Kasper24
+-------------------------------------------
+-- easing
+-- Adapted from https://github.com/EmmanuelOga/easing. See LICENSE.txt for credits.
+-- For all easing functions:
+-- t = time == how much time has to pass for the tweening to complete
+-- b = begin == starting property value
+-- c = change == ending - beginning
+-- d = duration == running time. How much time has passed *right now*
+-- local Color = require "external.lua-color"
+local gobject = require "gears.object"
+local gtable = require "gears.table"
+local tostring = tostring
+local assert = assert
+local table = table
+local pairs = pairs
+local error = error
+
+local type = type
+
+local tween = {
+ _VERSION = "tween 2.1.1",
+ _DESCRIPTION = "tweening for lua",
+ _URL = "https://github.com/kikito/tween.lua",
+ _LICENSE = [[
+ MIT LICENSE
+ Copyright (c) 2014 Enrique García Cota, Yuichi Tateno, Emmanuel Oga
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+ The above copyright notice and this permission notice shall be included
+ in all copies or substantial portions of the Software.
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ ]],
+}
+
+local pow, sin, cos, pi, sqrt, abs, asin = math.pow, math.sin, math.cos, math.pi, math.sqrt, math.abs, math.asin
+
+-- linear
+local function linear(t, b, c, d)
+ return c * t / d + b
+end
+
+-- quad
+local function inQuad(t, b, c, d)
+ return c * pow(t / d, 2) + b
+end
+local function outQuad(t, b, c, d)
+ t = t / d
+ return -c * t * (t - 2) + b
+end
+local function inOutQuad(t, b, c, d)
+ t = t / d * 2
+ if t < 1 then
+ return c / 2 * pow(t, 2) + b
+ end
+ return -c / 2 * ((t - 1) * (t - 3) - 1) + b
+end
+local function outInQuad(t, b, c, d)
+ if t < d / 2 then
+ return outQuad(t * 2, b, c / 2, d)
+ end
+ return inQuad((t * 2) - d, b + c / 2, c / 2, d)
+end
+
+-- cubic
+local function inCubic(t, b, c, d)
+ return c * pow(t / d, 3) + b
+end
+local function outCubic(t, b, c, d)
+ return c * (pow(t / d - 1, 3) + 1) + b
+end
+local function inOutCubic(t, b, c, d)
+ t = t / d * 2
+ if t < 1 then
+ return c / 2 * t * t * t + b
+ end
+ t = t - 2
+ return c / 2 * (t * t * t + 2) + b
+end
+local function outInCubic(t, b, c, d)
+ if t < d / 2 then
+ return outCubic(t * 2, b, c / 2, d)
+ end
+ return inCubic((t * 2) - d, b + c / 2, c / 2, d)
+end
+
+-- quart
+local function inQuart(t, b, c, d)
+ return c * pow(t / d, 4) + b
+end
+local function outQuart(t, b, c, d)
+ return -c * (pow(t / d - 1, 4) - 1) + b
+end
+local function inOutQuart(t, b, c, d)
+ t = t / d * 2
+ if t < 1 then
+ return c / 2 * pow(t, 4) + b
+ end
+ return -c / 2 * (pow(t - 2, 4) - 2) + b
+end
+local function outInQuart(t, b, c, d)
+ if t < d / 2 then
+ return outQuart(t * 2, b, c / 2, d)
+ end
+ return inQuart((t * 2) - d, b + c / 2, c / 2, d)
+end
+
+-- quint
+local function inQuint(t, b, c, d)
+ return c * pow(t / d, 5) + b
+end
+local function outQuint(t, b, c, d)
+ return c * (pow(t / d - 1, 5) + 1) + b
+end
+local function inOutQuint(t, b, c, d)
+ t = t / d * 2
+ if t < 1 then
+ return c / 2 * pow(t, 5) + b
+ end
+ return c / 2 * (pow(t - 2, 5) + 2) + b
+end
+local function outInQuint(t, b, c, d)
+ if t < d / 2 then
+ return outQuint(t * 2, b, c / 2, d)
+ end
+ return inQuint((t * 2) - d, b + c / 2, c / 2, d)
+end
+
+-- sine
+local function inSine(t, b, c, d)
+ return -c * cos(t / d * (pi / 2)) + c + b
+end
+local function outSine(t, b, c, d)
+ return c * sin(t / d * (pi / 2)) + b
+end
+local function inOutSine(t, b, c, d)
+ return -c / 2 * (cos(pi * t / d) - 1) + b
+end
+local function outInSine(t, b, c, d)
+ if t < d / 2 then
+ return outSine(t * 2, b, c / 2, d)
+ end
+ return inSine((t * 2) - d, b + c / 2, c / 2, d)
+end
+
+-- expo
+local function inExpo(t, b, c, d)
+ if t == 0 then
+ return b
+ end
+ return c * pow(2, 10 * (t / d - 1)) + b - c * 0.001
+end
+local function outExpo(t, b, c, d)
+ if t == d then
+ return b + c
+ end
+ return c * 1.001 * (-pow(2, -10 * t / d) + 1) + b
+end
+local function inOutExpo(t, b, c, d)
+ if t == 0 then
+ return b
+ end
+ if t == d then
+ return b + c
+ end
+ t = t / d * 2
+ if t < 1 then
+ return c / 2 * pow(2, 10 * (t - 1)) + b - c * 0.0005
+ end
+ return c / 2 * 1.0005 * (-pow(2, -10 * (t - 1)) + 2) + b
+end
+local function outInExpo(t, b, c, d)
+ if t < d / 2 then
+ return outExpo(t * 2, b, c / 2, d)
+ end
+ return inExpo((t * 2) - d, b + c / 2, c / 2, d)
+end
+
+-- circ
+local function inCirc(t, b, c, d)
+ return (-c * (sqrt(1 - pow(t / d, 2)) - 1) + b)
+end
+local function outCirc(t, b, c, d)
+ return (c * sqrt(1 - pow(t / d - 1, 2)) + b)
+end
+local function inOutCirc(t, b, c, d)
+ t = t / d * 2
+ if t < 1 then
+ return -c / 2 * (sqrt(1 - t * t) - 1) + b
+ end
+ t = t - 2
+ return c / 2 * (sqrt(1 - t * t) + 1) + b
+end
+local function outInCirc(t, b, c, d)
+ if t < d / 2 then
+ return outCirc(t * 2, b, c / 2, d)
+ end
+ return inCirc((t * 2) - d, b + c / 2, c / 2, d)
+end
+
+-- elastic
+local function calculatePAS(p, a, c, d)
+ p, a = p or d * 0.3, a or 0
+ if a < abs(c) then
+ return p, c, p / 4
+ end -- p, a, s
+ return p, a, p / (2 * pi) * asin(c / a) -- p,a,s
+end
+local function inElastic(t, b, c, d, a, p)
+ local s
+ if t == 0 then
+ return b
+ end
+ t = t / d
+ if t == 1 then
+ return b + c
+ end
+ p, a, s = calculatePAS(p, a, c, d)
+ t = t - 1
+ return -(a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b
+end
+local function outElastic(t, b, c, d, a, p)
+ local s
+ if t == 0 then
+ return b
+ end
+ t = t / d
+ if t == 1 then
+ return b + c
+ end
+ p, a, s = calculatePAS(p, a, c, d)
+ return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p) + c + b
+end
+local function inOutElastic(t, b, c, d, a, p)
+ local s
+ if t == 0 then
+ return b
+ end
+ t = t / d * 2
+ if t == 2 then
+ return b + c
+ end
+ p, a, s = calculatePAS(p, a, c, d)
+ t = t - 1
+ if t < 0 then
+ return -0.5 * (a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b
+ end
+ return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p) * 0.5 + c + b
+end
+local function outInElastic(t, b, c, d, a, p)
+ if t < d / 2 then
+ return outElastic(t * 2, b, c / 2, d, a, p)
+ end
+ return inElastic((t * 2) - d, b + c / 2, c / 2, d, a, p)
+end
+
+-- back
+local function inBack(t, b, c, d, s)
+ s = s or 1.70158
+ t = t / d
+ return c * t * t * ((s + 1) * t - s) + b
+end
+local function outBack(t, b, c, d, s)
+ s = s or 1.70158
+ t = t / d - 1
+ return c * (t * t * ((s + 1) * t + s) + 1) + b
+end
+local function inOutBack(t, b, c, d, s)
+ s = (s or 1.70158) * 1.525
+ t = t / d * 2
+ if t < 1 then
+ return c / 2 * (t * t * ((s + 1) * t - s)) + b
+ end
+ t = t - 2
+ return c / 2 * (t * t * ((s + 1) * t + s) + 2) + b
+end
+local function outInBack(t, b, c, d, s)
+ if t < d / 2 then
+ return outBack(t * 2, b, c / 2, d, s)
+ end
+ return inBack((t * 2) - d, b + c / 2, c / 2, d, s)
+end
+
+-- bounce
+local function outBounce(t, b, c, d)
+ t = t / d
+ if t < 1 / 2.75 then
+ return c * (7.5625 * t * t) + b
+ end
+ if t < 2 / 2.75 then
+ t = t - (1.5 / 2.75)
+ return c * (7.5625 * t * t + 0.75) + b
+ elseif t < 2.5 / 2.75 then
+ t = t - (2.25 / 2.75)
+ return c * (7.5625 * t * t + 0.9375) + b
+ end
+ t = t - (2.625 / 2.75)
+ return c * (7.5625 * t * t + 0.984375) + b
+end
+local function inBounce(t, b, c, d)
+ return c - outBounce(d - t, 0, c, d) + b
+end
+local function inOutBounce(t, b, c, d)
+ if t < d / 2 then
+ return inBounce(t * 2, 0, c, d) * 0.5 + b
+ end
+ return outBounce(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b
+end
+local function outInBounce(t, b, c, d)
+ if t < d / 2 then
+ return outBounce(t * 2, b, c / 2, d)
+ end
+ return inBounce((t * 2) - d, b + c / 2, c / 2, d)
+end
+
+tween.easing = {
+ linear = linear,
+ inQuad = inQuad,
+ outQuad = outQuad,
+ inOutQuad = inOutQuad,
+ outInQuad = outInQuad,
+ inCubic = inCubic,
+ outCubic = outCubic,
+ inOutCubic = inOutCubic,
+ outInCubic = outInCubic,
+ inQuart = inQuart,
+ outQuart = outQuart,
+ inOutQuart = inOutQuart,
+ outInQuart = outInQuart,
+ inQuint = inQuint,
+ outQuint = outQuint,
+ inOutQuint = inOutQuint,
+ outInQuint = outInQuint,
+ inSine = inSine,
+ outSine = outSine,
+ inOutSine = inOutSine,
+ outInSine = outInSine,
+ inExpo = inExpo,
+ outExpo = outExpo,
+ inOutExpo = inOutExpo,
+ outInExpo = outInExpo,
+ inCirc = inCirc,
+ outCirc = outCirc,
+ inOutCirc = inOutCirc,
+ outInCirc = outInCirc,
+ inElastic = inElastic,
+ outElastic = outElastic,
+ inOutElastic = inOutElastic,
+ outInElastic = outInElastic,
+ inBack = inBack,
+ outBack = outBack,
+ inOutBack = inOutBack,
+ outInBack = outInBack,
+ inBounce = inBounce,
+ outBounce = outBounce,
+ inOutBounce = inOutBounce,
+ outInBounce = outInBounce,
+}
+
+-- Private interface
+local function copyTables(destination, keysTable, valuesTable)
+ valuesTable = valuesTable or keysTable
+ local mt = getmetatable(keysTable)
+ if mt and getmetatable(destination) == nil then
+ setmetatable(destination, mt)
+ end
+
+ for k, v in pairs(keysTable) do
+ if type(v) == "table" then
+ destination[k] = copyTables({}, v, valuesTable[k])
+ else
+ destination[k] = valuesTable[k]
+ end
+ end
+ return destination
+end
+
+local function checkSubjectAndTargetRecursively(subject, target, path)
+ path = path or {}
+ local targetType, newPath
+ for k, targetValue in pairs(target) do
+ targetType, newPath = type(targetValue), copyTables({}, path)
+ table.insert(newPath, tostring(k))
+ if targetType == "number" then
+ assert(
+ type(subject[k]) == "number",
+ "Parameter '" .. table.concat(newPath, "/") .. "' is missing from subject or isn't a number"
+ )
+ elseif targetType == "table" then
+ checkSubjectAndTargetRecursively(subject[k], targetValue, newPath)
+ else
+ assert(
+ targetType == "number",
+ "Parameter '" .. table.concat(newPath, "/") .. "' must be a number or table of numbers"
+ )
+ end
+ end
+end
+
+local function checkNewParams(initial, duration, subject, target, easing)
+ -- assert(type(initial) == 'number' and duration > 0, "duration must be a positive number. Was " .. tostring(duration))
+ -- assert(type(duration) == 'number' and duration > 0, "duration must be a positive number. Was " .. tostring(duration))
+ assert(type(easing) == "function", "easing must be a function. Was " .. tostring(easing))
+
+ if subject and target then
+ local tsubject = type(subject)
+ assert(
+ tsubject == "table" or tsubject == "userdata",
+ "subject must be a table or userdata. Was " .. tostring(subject)
+ )
+ assert(type(target) == "table", "target must be a table. Was " .. tostring(target))
+ checkSubjectAndTargetRecursively(subject, target)
+ end
+end
+
+local function getEasingFunction(easing)
+ easing = easing or "linear"
+ if type(easing) == "string" then
+ local name = easing
+ easing = tween.easing[name]
+ if type(easing) ~= "function" then
+ error("The easing function name '" .. name .. "' is invalid")
+ end
+ end
+ return easing
+end
+
+local function performEasingOnSubject(subject, target, initial, clock, duration, easing)
+ local t, b, c, d
+ for k, v in pairs(target) do
+ if type(v) == "table" then
+ performEasingOnSubject(subject[k], v, initial[k], clock, duration, easing)
+ else
+ t, b, c, d = clock, initial[k], v - initial[k], duration
+ subject[k] = easing(t, b, c, d)
+ end
+ end
+end
+
+-- local function performEasingOnColor(initial, target, clock, duration, easing)
+-- initial = Color(initial)
+-- target = Color(target)
+--
+-- local r = easing(clock, initial.r, target.r - initial.r, duration)
+-- local g = easing(clock, initial.g, target.g - initial.g, duration)
+-- local b = easing(clock, initial.b, target.b - initial.b, duration)
+-- local a = easing(clock, initial.a, target.a - initial.a, duration)
+--
+-- return tostring(Color { r = r, g = g, b = b, a = a })
+-- end
+
+local function performEasing(table, initial, target, clock, duration, easing)
+ if type(target) == "table" then
+ local t, b, c, d
+ for k, target in pairs(target) do
+ if type(target) == "table" then
+ table[k] = {}
+ performEasing(table[k], initial[k], target, clock, duration, easing)
+ -- elseif type(target) == "string" and target:sub(1, 1) == "#" then
+ -- table[k] = performEasingOnColor(initial[k], target, clock, duration, easing)
+ else
+ t, b, c, d = clock, initial[k], target - initial[k], duration
+ table[k] = easing(t, b, c, d)
+ end
+ end
+
+ return table
+ -- elseif type(target) == "string" and target:sub(1, 1) == "#" then
+ -- return performEasingOnColor(initial, target, clock, duration, easing)
+ else
+ local t, b, c, d = clock, initial, target - initial, duration
+ return easing(t, b, c, d)
+ end
+end
+
+-- Public interface
+local Tween = {}
+
+function Tween:set(clock)
+ assert(type(clock) == "number", "clock must be a positive number or 0")
+
+ if self.subject and self.initial == 0 then
+ self.initial = copyTables({}, self.target, self.subject)
+ end
+
+ self.clock = clock
+
+ if self.clock <= 0 then
+ self.clock = 0
+ if self.subject then
+ copyTables(self.subject, self.initial)
+ end
+ elseif self.clock >= self.duration then -- the tween has expired
+ self.clock = self.duration
+
+ if self.subject then
+ copyTables(self.subject, self.target)
+ end
+ else
+ if self.subject then
+ performEasingOnSubject(self.subject, self.target, self.initial, self.clock, self.duration, self.easing)
+ else
+ local pos = {}
+ return performEasing(pos, self.initial, self.target, self.clock, self.duration, self.easing)
+ end
+ end
+
+ return self.clock >= self.duration
+end
+
+function Tween:update(dt)
+ assert(type(dt) == "number", "dt must be a number")
+ return self:set(self.clock + dt)
+end
+
+function Tween:reset()
+ return self:set(0)
+end
+
+function tween.new(args)
+ args = args or {}
+
+ args.initial = args.initial or 0
+ args.subject = args.subject or nil
+ args.target = args.target or nil
+ args.duration = args.duration or 0
+ args.easing = args.easing or nil
+
+ args.easing = getEasingFunction(args.easing)
+ checkNewParams(args.initial, args.duration, args.subject, args.target, args.easing)
+
+ local ret = gobject {}
+ ret.clock = 0
+
+ gtable.crush(ret, args, true)
+ gtable.crush(ret, Tween, true)
+
+ return ret
+end
+
+return tween
diff --git a/.config/awesome/quarrel/bezier.lua b/.config/awesome/quarrel/bezier.lua
deleted file mode 100644
index 4229961..0000000
--- a/.config/awesome/quarrel/bezier.lua
+++ /dev/null
@@ -1,343 +0,0 @@
----------------------------------------------------------------------------
---- A helper module for computations involving Bézier curves
---
--- @author Alex Belykh &lt;albel727@ngs.ru&gt;
--- @copyright 2021 Alex Belykh
--- @submodule gears.math
----------------------------------------------------------------------------
-
-local table_insert = table.insert
-
-local bezier = {}
-
---- Compute the value of a Bézier curve at a given value of the t parameter.
---
--- This function evaluates the given curve `B` of an arbitrary degree
--- at a given point t.
---
--- @tparam {number,...} c The table of control points of the curve.
--- @tparam number t The value of the t parameter to evaluate the curve at.
--- @treturn[1] number The value of `B(t)`.
--- @treturn[2] nil `nil`, if c is empty.
--- @staticfct gears.math.bezier.curve_evaluate_at
--- @see wibox.widget.graph.step_hook
-function bezier.curve_evaluate_at(c, t)
- local from = c
- local tmp = { nil, nil, nil, nil }
- while #from > 1 do
- for i = 1, #from - 1 do
- tmp[i] = from[i] * (1 - t) + from[i + 1] * t
- end
- tmp[#from] = nil
- from = tmp
- end
-
- return from[1]
-end
-
---- Split a Bézier curve into two curves at a given value of the t parameter.
---
--- This function splits the given curve `B` of an arbitrary degree at a point t
--- into two curves of the same degree `B_left` and `B_right`, such that
--- `B_left(0)=B(0)`, `B_left(1)=B(t)=B_right(0)`, `B_right(1)=B(1)`.
---
--- @tparam {number,...} c The table of control points of the curve.
--- @tparam number t The value of the t parameter to split the curve at.
--- @treturn {number,...} The table of control points for `B_left`.
--- @treturn {number,...} The table of control points for `B_right`.
--- @staticfct gears.math.bezier.curve_split_at
--- @see wibox.widget.graph.step_hook
-function bezier.curve_split_at(c, t)
- local coefs_left, coefs_right = {}, {}
- local from = c
- local tmp = { nil, nil, nil, nil }
- while #from > 0 do
- table_insert(coefs_left, from[1])
- table_insert(coefs_right, 1, from[#from])
- for i = 1, #from - 1 do
- tmp[i] = from[i] * (1 - t) + from[i + 1] * t
- end
- tmp[#from] = nil
- from = tmp
- end
-
- return coefs_left, coefs_right
-end
-
---- Get the n-th derivative Bézier curve of a Bézier curve.
---
--- This function computes control points for the curve that is
--- the derivative of order `n` in `t`, i.e. `B^(n)(t)`,
--- of the given curve `B(t)` of an arbitrary degree.
---
--- @tparam {number,...} c The table of control points of the curve.
--- @tparam[opt=1] integer n The order of the derivative to take.
--- @treturn[1] {number,...} The table of control points of `B^(n)(t)`.
--- @treturn[2] nil If n is less than 0.
--- @staticfct gears.math.bezier.curve_derivative
--- @see wibox.widget.graph.step_hook
-function bezier.curve_derivative(c, n)
- n = n or 1
- if n < 0 then
- return
- end
- if n < 1 then
- return c
- end
- local c_len = #c
- if c_len < n + 1 then
- return {}
- end
-
- local from = c
- local tmp = {}
-
- for l = c_len - 1, c_len - n, -1 do
- for i = 1, l do
- tmp[i] = (from[i + 1] - from[i]) * l
- end
- tmp[l + 1] = nil
- from = tmp
- end
-
- return from
-end
-
--- This is used instead of plain 0 to try and be compatible
--- with objects that implement their own arithmetic via metatables.
-local function get_zero(c, zero)
- return c and c * 0 or zero
-end
-
---- Compute the value of the n-th derivative of a Bézier curve
---- at a given value of the t parameter.
---
--- This is roughly the same as
--- `curve_evaluate_at(curve_derivative(c, n), t)`, but the latter
--- would throw errors or return nil instead of 0 in some cases.
---
--- @tparam {number,...} c The table of control points of the curve.
--- @tparam number t The value of the t parameter to compute the derivative at.
--- @tparam[opt=1] integer n The order of the derivative to take.
--- @tparam[opt=nil] number|nil zero The value to return if c is empty.
--- @treturn[1] number The value of `B^(n)(t)`.
--- @treturn[2] nil nil, if n is less than 0.
--- @treturn[3] number|nil The value of the zero parameter, if c is empty.
--- @staticfct gears.math.bezier.curve_derivative_at
--- @see wibox.widget.graph.step_hook
-function bezier.curve_derivative_at(c, t, n, zero)
- local d = bezier.curve_derivative(c, n)
- if not d then
- return
- end
-
- return bezier.curve_evaluate_at(d, t) or get_zero(c[1], zero)
-end
-
---- Compute the value of the 1-st derivative of a Bézier curve at t=0.
---
--- This is the same as `curve_derivative_at(c, 0)`, but since it's particularly
--- computationally simple and useful in practice, it has its own function.
---
--- @tparam {number,...} c The table of control points of the curve.
--- @tparam[opt=nil] number|nil zero The value to return if c is empty.
--- @treturn[1] number The value of `B'(0)`.
--- @treturn[2] number|nil The value of the zero parameter, if c is empty.
--- @staticfct gears.math.bezier.curve_derivative_at_zero
--- @see wibox.widget.graph.step_hook
-function bezier.curve_derivative_at_zero(c, zero)
- local l = #c
- if l < 2 then
- return get_zero(c[1], zero)
- end
- return (c[2] - c[1]) * (l - 1)
-end
-
---- Compute the value of the 1-st derivative of a Bézier curve at t=1.
---
--- This is the same as `curve_derivative_at(c, 1)`, but since it's particularly
--- computationally simple and useful in practice, it has its own function.
---
--- @tparam {number,...} c The table of control points of the curve.
--- @tparam[opt=nil] number|nil zero The value to return if c is empty.
--- @treturn[1] number The value of `B'(1)`.
--- @treturn[2] number|nil The value of the zero parameter, if c is empty.
--- @staticfct gears.math.bezier.curve_derivative_at_one
--- @see wibox.widget.graph.step_hook
-function bezier.curve_derivative_at_one(c, zero)
- local l = #c
- if l < 2 then
- return get_zero(c[1], zero)
- end
- return (c[l] - c[l - 1]) * (l - 1)
-end
-
---- Get the (n+1)-th degree Bézier curve, that has the same shape as
--- a given n-th degree Bézier curve.
---
--- Given the control points of a curve B of degree n, this function computes
--- the control points for the curve Q, such that `Q(t) = B(t)`, and
--- Q has the degree n+1, i.e. it has one control point more.
---
--- @tparam {number,...} c The table of control points of the curve B.
--- @treturn {number,...} The table of control points of the curve Q.
--- @staticfct gears.math.bezier.curve_elevate_degree
--- @see wibox.widget.graph.step_hook
-function bezier.curve_elevate_degree(c)
- local ret = { c[1] }
- local len = #c
-
- for i = 1, len - 1 do
- ret[i + 1] = (i * c[i] + (len - i) * c[i + 1]) / len
- end
-
- ret[len + 1] = c[len]
- return ret
-end
-
---- Get a cubic Bézier curve that passes through given points (up to 4).
---
--- This function takes up to 4 values and returns the 4 control points
--- for a cubic curve
---
--- `B(t) = c0\*(1-t)^3 + 3\*c1\*t\*(1-t)^2 + 3\*c2\*t^2\*(1-t) + c3\*t^3`,
--- that takes on these values at equidistant values of the t parameter.
---
--- If only p0 is given, `B(0)=B(1)=B(for all t)=p0`.
---
--- If p0 and p1 are given, `B(0)=p0` and `B(1)=p1`.
---
--- If p0, p1 and p2 are given, `B(0)=p0`, `B(1/2)=p1` and `B(1)=p2`.
---
--- For 4 points given, `B(0)=p0`, `B(1/3)=p1`, `B(2/3)=p2`, `B(1)=p3`.
---
--- @tparam number p0
--- @tparam[opt] number p1
--- @tparam[opt] number p2
--- @tparam[opt] number p3
--- @treturn number c0
--- @treturn number c1
--- @treturn number c2
--- @treturn number c3
--- @staticfct gears.math.bezier.cubic_through_points
--- @see wibox.widget.graph.step_hook
-function bezier.cubic_through_points(p0, p1, p2, p3)
- if not p1 then
- return p0, p0, p0, p0
- end
- if not p2 then
- local c1 = (2 * p0 + p1) / 3
- local c2 = (2 * p1 + p0) / 3
- return p0, c1, c2, p1
- end
- if not p3 then
- local c1 = (4 * p1 - p2) / 3
- local c2 = (4 * p1 - p0) / 3
- return p0, c1, c2, p2
- end
- local c1 = (-5 * p0 + 18 * p1 - 9 * p2 + 2 * p3) / 6
- local c2 = (-5 * p3 + 18 * p2 - 9 * p1 + 2 * p0) / 6
- return p0, c1, c2, p3
-end
-
---- Get a cubic Bézier curve with given values and derivatives at endpoints.
---
--- This function computes the 4 control points for the cubic curve B, such that
--- `B(0)=p0`, `B'(0)=d0`, `B(1)=p3`, `B'(1)=d3`.
---
--- @tparam number d0 The value of the derivative at t=0.
--- @tparam number p0 The value of the curve at t=0.
--- @tparam number p3 The value of the curve at t=1.
--- @tparam number d3 The value of the derivative at t=1.
--- @treturn number c0
--- @treturn number c1
--- @treturn number c2
--- @treturn number c3
--- @staticfct gears.math.bezier.cubic_from_points_and_derivatives
--- @see wibox.widget.graph.step_hook
-function bezier.cubic_from_points_and_derivatives(d0, p0, p3, d3)
- local c1 = p0 + d0 / 3
- local c2 = p3 - d3 / 3
- return p0, c1, c2, p3
-end
-
---- Get a cubic Bézier curve with given values at endpoints and starting
---- derivative, while minimizing (an approximation of) the stretch energy.
---
--- This function computes the 4 control points for the cubic curve B, such that
--- `B(0)=p0`, `B'(0)=d0`, `B(1)=p3`, and
--- the integral of `(B'(t))^2` on `t=[0,1]` is minimal.
--- (The actual stretch energy is the integral of `|B'(t)|`)
---
--- In practical terms this is almost the same as "the curve of shortest length
--- connecting given points and having the given starting speed".
---
--- @tparam number d0 The value of the derivative at t=0.
--- @tparam number p0 The value of the curve at t=0.
--- @tparam number p3 The value of the curve at t=1.
--- @treturn number c0
--- @treturn number c1
--- @treturn number c2
--- @treturn number c3
--- @staticfct gears.math.bezier.cubic_from_derivative_and_points_min_stretch
--- @see wibox.widget.graph.step_hook
-function bezier.cubic_from_derivative_and_points_min_stretch(d0, p0, p3)
- local c1 = p0 + d0 / 3
- local c2 = (2 * p0 - c1 + 3 * p3) / 4
- return p0, c1, c2, p3
-end
-
---- Get a cubic Bézier curve with given values at endpoints and starting
---- derivative, while minimizing (an approximation of) the strain energy.
---
--- This function computes the 4 control points for the cubic curve B, such that
--- `B(0)=p0`, `B'(0)=d0`, `B(1)=p3`, and
--- the integral of `(B''(t))^2` on `t=[0,1]` is minimal.
---
--- In practical terms this is almost the same as "the curve of smallest
--- speed change connecting given points and having the given starting speed".
---
--- @tparam number d0 The value of the derivative at t=0.
--- @tparam number p0 The value of the curve at t=0.
--- @tparam number p3 The value of the curve at t=1.
--- @treturn number c0
--- @treturn number c1
--- @treturn number c2
--- @treturn number c3
--- @staticfct gears.math.bezier.cubic_from_derivative_and_points_min_strain
--- @see wibox.widget.graph.step_hook
-function bezier.cubic_from_derivative_and_points_min_strain(d, p0, p3)
- local c1 = p0 + d / 3
- local c2 = (c1 + p3) / 2
- return p0, c1, c2, p3
-end
-
---- Get a cubic Bézier curve with given values at endpoints and starting
---- derivative, while minimizing the jerk energy.
---
--- This function computes the 4 control points for the cubic curve B, such that
--- `B(0)=p0`, `B'(0)=d0`, `B(1)=p3`, and
--- the integral of `(B'''(t))^2` on `t=[0,1]` is minimal.
---
--- In practical terms this is almost the same as "the curve of smallest
--- acceleration change connecting given points and having the given
--- starting speed".
---
--- @tparam number d0 The value of the derivative at t=0.
--- @tparam number p0 The value of the curve at t=0.
--- @tparam number p3 The value of the curve at t=1.
--- @treturn number c0
--- @treturn number c1
--- @treturn number c2
--- @treturn number c3
--- @staticfct gears.math.bezier.cubic_from_derivative_and_points_min_jerk
--- @see wibox.widget.graph.step_hook
-function bezier.cubic_from_derivative_and_points_min_jerk(d, p0, p3)
- local c1 = p0 + d / 3
- local c2 = c1 + (p3 - p0) / 3
- return p0, c1, c2, p3
-end
-
-return bezier
-
--- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/.config/awesome/quarrel/bind/consts.lua b/.config/awesome/quarrel/bind/consts.lua
new file mode 100644
index 0000000..5a91d84
--- /dev/null
+++ b/.config/awesome/quarrel/bind/consts.lua
@@ -0,0 +1,27 @@
+local awful = require "awful"
+
+local C = {}
+
+-- taken from https://github.com/bew/dotfiles/blob/ab9bb1935783f7a31ef777b1d7e26d53f35df864/gui/wezterm/cfg_utils.lua
+C.mods = setmetatable({ _SHORT_MAP = { C = "Control", S = "Shift", A = "Mod1", M = "Mod4" } }, {
+ __index = function(self, key)
+ local resolved_mods = {}
+ for i = 1, #key do
+ resolved_mods[i] = self._SHORT_MAP[key:sub(i, i)]
+ end
+ return resolved_mods
+ end,
+})
+
+local btns = awful.button.names
+
+---@enum buttons
+C.btns = {
+ left = btns.LEFT,
+ right = btns.RIGHT,
+ middle = btns.MIDDLE,
+ up = btns.SCROLL_UP,
+ down = btns.SCROLL_DOWN,
+}
+
+return C
diff --git a/.config/awesome/quarrel/bind.lua b/.config/awesome/quarrel/bind/init.lua
index a1abf29..a851440 100644
--- a/.config/awesome/quarrel/bind.lua
+++ b/.config/awesome/quarrel/bind/init.lua
@@ -1,11 +1,10 @@
+local C = require "quarrel.bind.consts"
local awful = require "awful"
local gtable = require "gears.table"
-local qstore = require "quarrel.store"
----@class QuarrelBind
-local qbind = {}
-
-qstore.bindings = {}
+local M = gtable.crush({
+ bindings = {},
+}, C)
---@alias mouse_button
---| 0 Left mouse button
@@ -35,7 +34,7 @@ qstore.bindings = {}
---@field press fun(...) | fun(any, ...) Function to run when the trigger is pressed
---@field desc string? Description
---@field group string? What group the binding will show up in
----@field triggers Trigger[] | bind
+---@field triggers Trigger[] | bind[] | bind
---Get the corresponding binding creation function for a trigger
---@param bind bind
@@ -83,24 +82,30 @@ end
--- Create a new binding
---@param binding Binding
---@return awful.key[]
-function qbind:new(binding)
+function M:new(binding)
if not binding.hidden then
- table.insert(qstore.bindings, binding)
+ table.insert(self.bindings, binding)
end
binding.mods = binding.mods or {}
local awful_bindings = {}
if type(binding.triggers) == "table" then
- for _, trigger in
- ipairs(binding.triggers --[[@as Trigger[]])
+ for _, _trigger in
+ ipairs(binding.triggers --[[@as bind[] | Trigger[]])
do
+ local trigger
+ if type(_trigger) == "table" then
+ trigger = _trigger
+ elseif type(_trigger) == "string" or type(_trigger) == "number" then
+ trigger = { _trigger, _trigger }
+ end
table.insert(awful_bindings, translate_binding(binding, trigger, true))
end
elseif type(binding.triggers) == "string" or type(binding.triggers) == "number" then
return translate_binding(binding, binding.triggers --[[@as bind]], false)
else
- error "binding.triggers can only be a string or a table"
+ error("binding.triggers can only be a string or a table (passed in " .. type(binding.triggers) .. ")")
end
-- for some reason multi-trigger bindings only work if i do this
@@ -109,4 +114,6 @@ function qbind:new(binding)
return gtable.join(table.unpack(awful_bindings))
end
-return qbind
+return setmetatable(M, {
+ __call = M.new,
+})
diff --git a/.config/awesome/quarrel/color.lua b/.config/awesome/quarrel/color.lua
new file mode 100644
index 0000000..b96ef57
--- /dev/null
+++ b/.config/awesome/quarrel/color.lua
@@ -0,0 +1,64 @@
+-- autogenerated from palette.json
+local M = {}
+
+local function tone_set(colors)
+ return setmetatable(colors, {
+ __call = function(self)
+ return self[1]
+ end,
+ })
+end
+
+M.palette = {
+ bg = tone_set {
+ high = "#171c22",
+ highest = "#1d2228",
+ low = "#0c1116",
+ lowest = "#070c11",
+ "#12161c",
+ },
+ blue = tone_set {
+ bright = "#abe4ff",
+ "#8bc3fc",
+ },
+ border = tone_set {
+ "#323b46",
+ variant = "#262f39",
+ },
+ cyan = tone_set {
+ bright = "#8ef4e2",
+ "#6dd3c2",
+ },
+ fg = tone_set {
+ high = "#e8eff8",
+ low = "#a8afb7",
+ "#c8ced7",
+ },
+ green = tone_set {
+ bright = "#abf3b3",
+ "#8bd294",
+ },
+ orange = tone_set {
+ bright = "#ffc08e",
+ "#ff9f6f",
+ },
+ pink = tone_set {
+ bright = "#ffccd4",
+ "#e5acb4",
+ },
+ purple = tone_set {
+ bright = "#e4d1ff",
+ "#c4b1f6",
+ },
+ red = tone_set {
+ bright = "#ffb2a9",
+ "#ff928a",
+ },
+ yellow = tone_set {
+ bright = "#ffd278",
+ "#ecb256",
+ },
+ transparent = "#00000000",
+}
+
+return M
diff --git a/.config/awesome/quarrel/const.lua b/.config/awesome/quarrel/const.lua
new file mode 100644
index 0000000..342ae41
--- /dev/null
+++ b/.config/awesome/quarrel/const.lua
@@ -0,0 +1,22 @@
+local M = {}
+
+---@param protected (fun(): table) | table
+function M.protect(protected)
+ ---@type table
+ local tbl
+ if type(protected) == "table" then
+ tbl = protected
+ elseif type(protected) == "function" then
+ tbl = protected()
+ else
+ error("expected a table or a function that returns a table, got " .. type(protected), 2)
+ end
+ return setmetatable({}, {
+ __index = tbl,
+ __newindex = function(_, k, v)
+ error("attempted to change constant " .. tostring(k) .. " to " .. tostring(v), 2)
+ end,
+ })
+end
+
+return M
diff --git a/.config/awesome/quarrel/debugger.lua b/.config/awesome/quarrel/debugger.lua
new file mode 100644
index 0000000..b1a272a
--- /dev/null
+++ b/.config/awesome/quarrel/debugger.lua
@@ -0,0 +1,865 @@
+-- SPDX-License-Identifier: MIT
+-- Copyright (c) 2024 Scott Lembcke and Howling Moon Software
+
+local dbg
+
+-- Use ANSI color codes in the prompt by default.
+local COLOR_GRAY = ""
+local COLOR_RED = ""
+local COLOR_BLUE = ""
+local COLOR_YELLOW = ""
+local COLOR_RESET = ""
+local GREEN_CARET = " => "
+
+local function pretty(obj, max_depth)
+ if max_depth == nil then
+ max_depth = dbg.pretty_depth
+ end
+
+ -- Returns true if a table has a __tostring metamethod.
+ local function coerceable(tbl)
+ local meta = getmetatable(tbl)
+ return (meta and meta.__tostring)
+ end
+
+ local function recurse(obj, depth)
+ if type(obj) == "string" then
+ -- Dump the string so that escape sequences are printed.
+ return string.format("%q", obj)
+ elseif type(obj) == "table" and depth < max_depth and not coerceable(obj) then
+ local str = "{"
+
+ for k, v in pairs(obj) do
+ local pair = pretty(k, 0) .. " = " .. recurse(v, depth + 1)
+ str = str .. (str == "{" and pair or ", " .. pair)
+ end
+
+ return str .. "}"
+ else
+ -- tostring() can fail if there is an error in a __tostring metamethod.
+ local success, value = pcall(function()
+ return tostring(obj)
+ end)
+ return (success and value or "<!!error in __tostring metamethod!!>")
+ end
+ end
+
+ return recurse(obj, 0)
+end
+
+-- The stack level that cmd_* functions use to access locals or info
+-- The structure of the code very carefully ensures this.
+local CMD_STACK_LEVEL = 6
+
+-- Location of the top of the stack outside of the debugger.
+-- Adjusted by some debugger entrypoints.
+local stack_top = 0
+
+-- The current stack frame index.
+-- Changed using the up/down commands
+local stack_inspect_offset = 0
+
+-- LuaJIT has an off by one bug when setting local variables.
+local LUA_JIT_SETLOCAL_WORKAROUND = 0
+
+-- Default dbg.read function
+local function dbg_read(prompt)
+ dbg.write(prompt)
+ io.flush()
+ return io.read()
+end
+
+-- Default dbg.write function
+local function dbg_write(str)
+ io.write(str)
+end
+
+local function dbg_writeln(str, ...)
+ if select("#", ...) == 0 then
+ dbg.write((str or "<NULL>") .. "\n")
+ else
+ dbg.write(string.format(str .. "\n", ...))
+ end
+end
+
+local function format_loc(file, line)
+ return COLOR_BLUE .. file .. COLOR_RESET .. ":" .. COLOR_YELLOW .. line .. COLOR_RESET
+end
+local function format_stack_frame_info(info)
+ local filename = info.source:match "@(.*)"
+ local source = filename and dbg.shorten_path(filename) or info.short_src
+ local namewhat = (info.namewhat == "" and "chunk at" or info.namewhat)
+ local name = (
+ info.name and "'" .. COLOR_BLUE .. info.name .. COLOR_RESET .. "'" or format_loc(source, info.linedefined)
+ )
+ return format_loc(source, info.currentline) .. " in " .. namewhat .. " " .. name
+end
+
+local repl
+
+-- Return false for stack frames without source,
+-- which includes C frames, Lua bytecode, and `loadstring` functions
+local function frame_has_line(info)
+ return info.currentline >= 0
+end
+
+local function hook_factory(repl_threshold)
+ return function(offset, reason)
+ return function(event, _)
+ -- Skip events that don't have line information.
+ if not frame_has_line(debug.getinfo(2)) then
+ return
+ end
+
+ -- Tail calls are specifically ignored since they also will have tail returns to balance out.
+ if event == "call" then
+ offset = offset + 1
+ elseif event == "return" and offset > repl_threshold then
+ offset = offset - 1
+ elseif event == "line" and offset <= repl_threshold then
+ repl(reason)
+ end
+ end
+ end
+end
+
+local hook_step = hook_factory(1)
+local hook_next = hook_factory(0)
+local hook_finish = hook_factory(-1)
+
+-- Create a table of all the locally accessible variables.
+-- Globals are not included when running the locals command, but are when running the print command.
+local function local_bindings(offset, include_globals)
+ local level = offset + stack_inspect_offset + CMD_STACK_LEVEL
+ local func = debug.getinfo(level).func
+ local bindings = {}
+
+ -- Retrieve the upvalues
+ do
+ local i = 1
+ while true do
+ local name, value = debug.getupvalue(func, i)
+ if not name then
+ break
+ end
+ bindings[name] = value
+ i = i + 1
+ end
+ end
+
+ -- Retrieve the locals (overwriting any upvalues)
+ do
+ local i = 1
+ while true do
+ local name, value = debug.getlocal(level, i)
+ if not name then
+ break
+ end
+ bindings[name] = value
+ i = i + 1
+ end
+ end
+
+ -- Retrieve the varargs (works in Lua 5.2 and LuaJIT)
+ local varargs = {}
+ do
+ local i = 1
+ while true do
+ local name, value = debug.getlocal(level, -i)
+ if not name then
+ break
+ end
+ varargs[i] = value
+ i = i + 1
+ end
+ end
+ if #varargs > 0 then
+ bindings["..."] = varargs
+ end
+
+ if include_globals then
+ -- In Lua 5.2, you have to get the environment table from the function's locals.
+ local env = (_VERSION <= "Lua 5.1" and getfenv(func) or bindings._ENV)
+ return setmetatable(bindings, { __index = env or _G })
+ else
+ return bindings
+ end
+end
+
+-- Used as a __newindex metamethod to modify variables in cmd_eval().
+local function mutate_bindings(_, name, value)
+ local FUNC_STACK_OFFSET = 3 -- Stack depth of this function.
+ local level = stack_inspect_offset + FUNC_STACK_OFFSET + CMD_STACK_LEVEL
+
+ -- Set a local.
+ do
+ local i = 1
+ repeat
+ local var = debug.getlocal(level, i)
+ if name == var then
+ dbg_writeln(
+ COLOR_YELLOW
+ .. "debugger.lua"
+ .. GREEN_CARET
+ .. "Set local variable "
+ .. COLOR_BLUE
+ .. name
+ .. COLOR_RESET
+ )
+ return debug.setlocal(level + LUA_JIT_SETLOCAL_WORKAROUND, i, value)
+ end
+ i = i + 1
+ until var == nil
+ end
+
+ -- Set an upvalue.
+ local func = debug.getinfo(level).func
+ do
+ local i = 1
+ repeat
+ local var = debug.getupvalue(func, i)
+ if name == var then
+ dbg_writeln(
+ COLOR_YELLOW .. "debugger.lua" .. GREEN_CARET .. "Set upvalue " .. COLOR_BLUE .. name .. COLOR_RESET
+ )
+ return debug.setupvalue(func, i, value)
+ end
+ i = i + 1
+ until var == nil
+ end
+
+ -- Set a global.
+ dbg_writeln(
+ COLOR_YELLOW .. "debugger.lua" .. GREEN_CARET .. "Set global variable " .. COLOR_BLUE .. name .. COLOR_RESET
+ )
+ _G[name] = value
+end
+
+-- Compile an expression with the given variable bindings.
+local function compile_chunk(block, env)
+ local source = "debugger.lua REPL"
+ local chunk = nil
+
+ if _VERSION <= "Lua 5.1" then
+ chunk = loadstring(block, source)
+ if chunk then
+ setfenv(chunk, env)
+ end
+ else
+ -- The Lua 5.2 way is a bit cleaner
+ chunk = load(block, source, "t", env)
+ end
+
+ if not chunk then
+ dbg_writeln(COLOR_RED .. "Error: Could not compile block:\n" .. COLOR_RESET .. block)
+ end
+ return chunk
+end
+
+local SOURCE_CACHE = {}
+
+local function where(info, context_lines)
+ local source = SOURCE_CACHE[info.source]
+ if not source then
+ source = {}
+ local filename = info.source:match "@(.*)"
+ if filename then
+ pcall(function()
+ for line in io.lines(filename) do
+ table.insert(source, line)
+ end
+ end)
+ elseif info.source then
+ for line in info.source:gmatch "[^\n]+" do
+ table.insert(source, line)
+ end
+ end
+ SOURCE_CACHE[info.source] = source
+ end
+
+ if source and source[info.currentline] then
+ for i = info.currentline - context_lines, info.currentline + context_lines do
+ local tab_or_caret = (i == info.currentline and GREEN_CARET or " ")
+ local line = source[i]
+ if line then
+ dbg_writeln(COLOR_GRAY .. "% 4d" .. tab_or_caret .. "%s", i, line)
+ end
+ end
+ else
+ dbg_writeln(COLOR_RED .. "Error: Source not available for " .. COLOR_BLUE .. info.short_src)
+ end
+
+ return false
+end
+
+-- Wee version differences
+local unpack = unpack or table.unpack
+local pack = function(...)
+ return { n = select("#", ...), ... }
+end
+
+local function cmd_step()
+ stack_inspect_offset = stack_top
+ return true, hook_step
+end
+
+local function cmd_next()
+ stack_inspect_offset = stack_top
+ return true, hook_next
+end
+
+local function cmd_finish()
+ local offset = stack_top - stack_inspect_offset
+ stack_inspect_offset = stack_top
+ return true, offset < 0 and hook_factory(offset - 1) or hook_finish
+end
+
+local function cmd_print(expr)
+ local env = local_bindings(1, true)
+ local chunk = compile_chunk("return " .. expr, env)
+ if chunk == nil then
+ return false
+ end
+
+ -- Call the chunk and collect the results.
+ local results = pack(pcall(chunk, unpack(rawget(env, "...") or {})))
+
+ -- The first result is the pcall error.
+ if not results[1] then
+ dbg_writeln(COLOR_RED .. "Error:" .. COLOR_RESET .. " " .. results[2])
+ else
+ local output = ""
+ for i = 2, results.n do
+ output = output .. (i ~= 2 and ", " or "") .. dbg.pretty(results[i])
+ end
+
+ if output == "" then
+ output = "<no result>"
+ end
+ dbg_writeln(COLOR_BLUE .. expr .. GREEN_CARET .. output)
+ end
+
+ return false
+end
+
+local function cmd_eval(code)
+ local env = local_bindings(1, true)
+ local mutable_env = setmetatable({}, {
+ __index = env,
+ __newindex = mutate_bindings,
+ })
+
+ local chunk = compile_chunk(code, mutable_env)
+ if chunk == nil then
+ return false
+ end
+
+ -- Call the chunk and collect the results.
+ local success, err = pcall(chunk, unpack(rawget(env, "...") or {}))
+ if not success then
+ dbg_writeln(COLOR_RED .. "Error:" .. COLOR_RESET .. " " .. tostring(err))
+ end
+
+ return false
+end
+
+local function cmd_down()
+ local offset = stack_inspect_offset
+ local info
+
+ repeat -- Find the next frame with a file.
+ offset = offset + 1
+ info = debug.getinfo(offset + CMD_STACK_LEVEL)
+ until not info or frame_has_line(info)
+
+ if info then
+ stack_inspect_offset = offset
+ dbg_writeln("Inspecting frame: " .. format_stack_frame_info(info))
+ if tonumber(dbg.auto_where) then
+ where(info, dbg.auto_where)
+ end
+ else
+ info = debug.getinfo(stack_inspect_offset + CMD_STACK_LEVEL)
+ dbg_writeln "Already at the bottom of the stack."
+ end
+
+ return false
+end
+
+local function cmd_up()
+ local offset = stack_inspect_offset
+ local info
+
+ repeat -- Find the next frame with a file.
+ offset = offset - 1
+ if offset < stack_top then
+ info = nil
+ break
+ end
+ info = debug.getinfo(offset + CMD_STACK_LEVEL)
+ until frame_has_line(info)
+
+ if info then
+ stack_inspect_offset = offset
+ dbg_writeln("Inspecting frame: " .. format_stack_frame_info(info))
+ if tonumber(dbg.auto_where) then
+ where(info, dbg.auto_where)
+ end
+ else
+ info = debug.getinfo(stack_inspect_offset + CMD_STACK_LEVEL)
+ dbg_writeln "Already at the top of the stack."
+ end
+
+ return false
+end
+
+local function cmd_inspect(offset)
+ offset = stack_top + tonumber(offset)
+ local info = debug.getinfo(offset + CMD_STACK_LEVEL)
+ if info then
+ stack_inspect_offset = offset
+ dbg.writeln("Inspecting frame: " .. format_stack_frame_info(info))
+ else
+ dbg.writeln(COLOR_RED .. "ERROR: " .. COLOR_BLUE .. "Invalid stack frame index." .. COLOR_RESET)
+ end
+end
+
+local function cmd_where(context_lines)
+ local info = debug.getinfo(stack_inspect_offset + CMD_STACK_LEVEL)
+ return (info and where(info, tonumber(context_lines) or 5))
+end
+
+local function cmd_trace()
+ dbg_writeln("Inspecting frame %d", stack_inspect_offset - stack_top)
+ local i = 0
+ while true do
+ local info = debug.getinfo(stack_top + CMD_STACK_LEVEL + i)
+ if not info then
+ break
+ end
+
+ local is_current_frame = (i + stack_top == stack_inspect_offset)
+ local tab_or_caret = (is_current_frame and GREEN_CARET or " ")
+ dbg_writeln(COLOR_GRAY .. "% 4d" .. COLOR_RESET .. tab_or_caret .. "%s", i, format_stack_frame_info(info))
+ i = i + 1
+ end
+
+ return false
+end
+
+local function cmd_locals()
+ local bindings = local_bindings(1, false)
+
+ -- Get all the variable binding names and sort them
+ local keys = {}
+ for k, _ in pairs(bindings) do
+ table.insert(keys, k)
+ end
+ table.sort(keys)
+
+ for _, k in ipairs(keys) do
+ local v = bindings[k]
+
+ -- Skip the debugger object itself, "(*internal)" values, and Lua 5.2's _ENV object.
+ if not rawequal(v, dbg) and k ~= "_ENV" and not k:match "%(.*%)" then
+ dbg_writeln(" " .. COLOR_BLUE .. k .. GREEN_CARET .. dbg.pretty(v))
+ end
+ end
+
+ return false
+end
+
+local function cmd_help()
+ dbg.write(
+ ""
+ .. COLOR_BLUE
+ .. " <return>"
+ .. GREEN_CARET
+ .. "re-run last command\n"
+ .. COLOR_BLUE
+ .. " c"
+ .. COLOR_YELLOW
+ .. "(ontinue)"
+ .. GREEN_CARET
+ .. "continue execution\n"
+ .. COLOR_BLUE
+ .. " s"
+ .. COLOR_YELLOW
+ .. "(tep)"
+ .. GREEN_CARET
+ .. "step forward by one line (into functions)\n"
+ .. COLOR_BLUE
+ .. " n"
+ .. COLOR_YELLOW
+ .. "(ext)"
+ .. GREEN_CARET
+ .. "step forward by one line (skipping over functions)\n"
+ .. COLOR_BLUE
+ .. " f"
+ .. COLOR_YELLOW
+ .. "(inish)"
+ .. GREEN_CARET
+ .. "step forward until exiting the current function\n"
+ .. COLOR_BLUE
+ .. " u"
+ .. COLOR_YELLOW
+ .. "(p)"
+ .. GREEN_CARET
+ .. "move up the stack by one frame\n"
+ .. COLOR_BLUE
+ .. " d"
+ .. COLOR_YELLOW
+ .. "(own)"
+ .. GREEN_CARET
+ .. "move down the stack by one frame\n"
+ .. COLOR_BLUE
+ .. " i"
+ .. COLOR_YELLOW
+ .. "(nspect) "
+ .. COLOR_BLUE
+ .. "[index]"
+ .. GREEN_CARET
+ .. "move to a specific stack frame\n"
+ .. COLOR_BLUE
+ .. " w"
+ .. COLOR_YELLOW
+ .. "(here) "
+ .. COLOR_BLUE
+ .. "[line count]"
+ .. GREEN_CARET
+ .. "print source code around the current line\n"
+ .. COLOR_BLUE
+ .. " e"
+ .. COLOR_YELLOW
+ .. "(val) "
+ .. COLOR_BLUE
+ .. "[statement]"
+ .. GREEN_CARET
+ .. "execute the statement\n"
+ .. COLOR_BLUE
+ .. " p"
+ .. COLOR_YELLOW
+ .. "(rint) "
+ .. COLOR_BLUE
+ .. "[expression]"
+ .. GREEN_CARET
+ .. "execute the expression and print the result\n"
+ .. COLOR_BLUE
+ .. " t"
+ .. COLOR_YELLOW
+ .. "(race)"
+ .. GREEN_CARET
+ .. "print the stack trace\n"
+ .. COLOR_BLUE
+ .. " l"
+ .. COLOR_YELLOW
+ .. "(ocals)"
+ .. GREEN_CARET
+ .. "print the function arguments, locals and upvalues.\n"
+ .. COLOR_BLUE
+ .. " h"
+ .. COLOR_YELLOW
+ .. "(elp)"
+ .. GREEN_CARET
+ .. "print this message\n"
+ .. COLOR_BLUE
+ .. " q"
+ .. COLOR_YELLOW
+ .. "(uit)"
+ .. GREEN_CARET
+ .. "halt execution\n"
+ )
+ return false
+end
+
+local last_cmd = false
+
+local commands = {
+ ["^c$"] = function()
+ return true
+ end,
+ ["^s$"] = cmd_step,
+ ["^n$"] = cmd_next,
+ ["^f$"] = cmd_finish,
+ ["^p%s+(.*)$"] = cmd_print,
+ ["^e%s+(.*)$"] = cmd_eval,
+ ["^u$"] = cmd_up,
+ ["^d$"] = cmd_down,
+ ["i%s*(%d+)"] = cmd_inspect,
+ ["^w%s*(%d*)$"] = cmd_where,
+ ["^t$"] = cmd_trace,
+ ["^l$"] = cmd_locals,
+ ["^h$"] = cmd_help,
+ ["^q$"] = function()
+ dbg.exit(0)
+ return true
+ end,
+}
+
+local function match_command(line)
+ for pat, func in pairs(commands) do
+ -- Return the matching command and capture argument.
+ if line:find(pat) then
+ return func, line:match(pat)
+ end
+ end
+end
+
+-- Run a command line
+-- Returns true if the REPL should exit and the hook function factory
+local function run_command(line)
+ -- GDB/LLDB exit on ctrl-d
+ if line == nil then
+ dbg.exit(1)
+ return true
+ end
+
+ -- Re-execute the last command if you press return.
+ if line == "" then
+ line = last_cmd or "h"
+ end
+
+ local command, command_arg = match_command(line)
+ if command then
+ last_cmd = line
+ -- unpack({...}) prevents tail call elimination so the stack frame indices are predictable.
+ return unpack { command(command_arg) }
+ elseif dbg.auto_eval then
+ return unpack { cmd_eval(line) }
+ else
+ dbg_writeln(
+ COLOR_RED
+ .. "Error:"
+ .. COLOR_RESET
+ .. " command '%s' not recognized.\nType 'h' and press return for a command list.",
+ line
+ )
+ return false
+ end
+end
+
+repl = function(reason)
+ -- Skip frames without source info.
+ while not frame_has_line(debug.getinfo(stack_inspect_offset + CMD_STACK_LEVEL - 3)) do
+ stack_inspect_offset = stack_inspect_offset + 1
+ end
+
+ local info = debug.getinfo(stack_inspect_offset + CMD_STACK_LEVEL - 3)
+ reason = reason and (COLOR_YELLOW .. "break via " .. COLOR_RED .. reason .. GREEN_CARET) or ""
+ dbg_writeln(reason .. format_stack_frame_info(info))
+
+ if tonumber(dbg.auto_where) then
+ where(info, dbg.auto_where)
+ end
+
+ repeat
+ local success, done, hook = pcall(run_command, dbg.read(COLOR_RED .. "debugger.lua> " .. COLOR_RESET))
+ if success then
+ debug.sethook(hook and hook(0), "crl")
+ else
+ local message = COLOR_RED .. "INTERNAL DEBUGGER.LUA ERROR. ABORTING\n:" .. COLOR_RESET .. " " .. done
+ dbg_writeln(message)
+ error(message)
+ end
+ until done
+end
+
+-- Make the debugger object callable like a function.
+dbg = setmetatable({}, {
+ __call = function(_, condition, top_offset, source)
+ if condition then
+ return
+ end
+
+ top_offset = (top_offset or 0)
+ stack_inspect_offset = top_offset
+ stack_top = top_offset
+
+ debug.sethook(hook_next(1, source or "dbg()"), "crl")
+ return
+ end,
+})
+
+-- Expose the debugger's IO functions.
+dbg.read = dbg_read
+dbg.write = dbg_write
+dbg.shorten_path = function(path)
+ return path
+end
+dbg.exit = function(err)
+ os.exit(err)
+end
+
+dbg.writeln = dbg_writeln
+
+dbg.pretty_depth = 3
+dbg.pretty = pretty
+dbg.pp = function(value, depth)
+ dbg_writeln(dbg.pretty(value, depth))
+end
+
+dbg.auto_where = false
+dbg.auto_eval = false
+
+local lua_error, lua_assert = error, assert
+
+-- Works like error(), but invokes the debugger.
+function dbg.error(err, level)
+ level = level or 1
+ dbg_writeln(COLOR_RED .. "ERROR: " .. COLOR_RESET .. dbg.pretty(err))
+ dbg(false, level, "dbg.error()")
+
+ lua_error(err, level)
+end
+
+-- Works like assert(), but invokes the debugger on a failure.
+function dbg.assert(condition, message)
+ message = message or "assertion failed!"
+ if not condition then
+ dbg_writeln(COLOR_RED .. "ERROR: " .. COLOR_RESET .. message)
+ dbg(false, 1, "dbg.assert()")
+ end
+
+ return lua_assert(condition, message)
+end
+
+-- Works like pcall(), but invokes the debugger on an error.
+function dbg.call(f, ...)
+ return xpcall(f, function(err)
+ dbg_writeln(COLOR_RED .. "ERROR: " .. COLOR_RESET .. dbg.pretty(err))
+ dbg(false, 1, "dbg.call()")
+
+ return err
+ end, ...)
+end
+
+-- Error message handler that can be used with lua_pcall().
+function dbg.msgh(...)
+ if debug.getinfo(2) then
+ dbg_writeln(COLOR_RED .. "ERROR: " .. COLOR_RESET .. dbg.pretty(...))
+ dbg(false, 1, "dbg.msgh()")
+ else
+ dbg_writeln(
+ COLOR_RED
+ .. "debugger.lua: "
+ .. COLOR_RESET
+ .. "Error did not occur in Lua code. Execution will continue after dbg_pcall()."
+ )
+ end
+
+ return ...
+end
+
+-- Assume stdin/out are TTYs unless we can use LuaJIT's FFI to properly check them.
+local stdin_isatty = true
+local stdout_isatty = true
+
+-- Conditionally enable the LuaJIT FFI.
+local ffi = (jit and require "ffi")
+if ffi then
+ ffi.cdef [[
+ int isatty(int); // Unix
+ int _isatty(int); // Windows
+ void free(void *ptr);
+
+ char *readline(const char *);
+ int add_history(const char *);
+ ]]
+
+ local function get_func_or_nil(sym)
+ local success, func = pcall(function()
+ return ffi.C[sym]
+ end)
+ return success and func or nil
+ end
+
+ local isatty = get_func_or_nil "isatty" or get_func_or_nil "_isatty" or (ffi.load "ucrtbase")["_isatty"]
+ stdin_isatty = isatty(0)
+ stdout_isatty = isatty(1)
+end
+
+-- Conditionally enable color support.
+local color_maybe_supported = (stdout_isatty and os.getenv "TERM" and os.getenv "TERM" ~= "dumb")
+if color_maybe_supported and not os.getenv "DBG_NOCOLOR" then
+ COLOR_GRAY = string.char(27) .. "[90m"
+ COLOR_RED = string.char(27) .. "[91m"
+ COLOR_BLUE = string.char(27) .. "[94m"
+ COLOR_YELLOW = string.char(27) .. "[33m"
+ COLOR_RESET = string.char(27) .. "[0m"
+ GREEN_CARET = string.char(27) .. "[92m => " .. COLOR_RESET
+end
+
+if stdin_isatty and not os.getenv "DBG_NOREADLINE" then
+ pcall(function()
+ local linenoise = require "linenoise"
+
+ -- Load command history from ~/.lua_history
+ local hist_path = os.getenv "HOME" .. "/.lua_history"
+ linenoise.historyload(hist_path)
+ linenoise.historysetmaxlen(50)
+
+ local function autocomplete(env, input, matches)
+ for name, _ in pairs(env) do
+ if name:match("^" .. input .. ".*") then
+ linenoise.addcompletion(matches, name)
+ end
+ end
+ end
+
+ -- Auto-completion for locals and globals
+ linenoise.setcompletion(function(matches, input)
+ -- First, check the locals and upvalues.
+ local env = local_bindings(1, true)
+ autocomplete(env, input, matches)
+
+ -- Then, check the implicit environment.
+ env = getmetatable(env).__index
+ autocomplete(env, input, matches)
+ end)
+
+ dbg.read = function(prompt)
+ local str = linenoise.linenoise(prompt)
+ if str and not str:match "^%s*$" then
+ linenoise.historyadd(str)
+ linenoise.historysave(hist_path)
+ end
+ return str
+ end
+ dbg_writeln(COLOR_YELLOW .. "debugger.lua: " .. COLOR_RESET .. "Linenoise support enabled.")
+ end)
+
+ -- Conditionally enable LuaJIT readline support.
+ pcall(function()
+ if dbg.read == dbg_read and ffi then
+ local readline = ffi.load "readline"
+ dbg.read = function(prompt)
+ local cstr = readline.readline(prompt)
+ if cstr ~= nil then
+ local str = ffi.string(cstr)
+ if string.match(str, "[^%s]+") then
+ readline.add_history(cstr)
+ end
+
+ ffi.C.free(cstr)
+ return str
+ else
+ return nil
+ end
+ end
+ dbg_writeln(COLOR_YELLOW .. "debugger.lua: " .. COLOR_RESET .. "Readline support enabled.")
+ end
+ end)
+end
+
+-- Detect Lua version.
+if jit then -- LuaJIT
+ LUA_JIT_SETLOCAL_WORKAROUND = -1
+ dbg_writeln(COLOR_YELLOW .. "debugger.lua: " .. COLOR_RESET .. "Loaded for " .. jit.version)
+elseif "Lua 5.1" <= _VERSION and _VERSION <= "Lua 5.4" then
+ dbg_writeln(COLOR_YELLOW .. "debugger.lua: " .. COLOR_RESET .. "Loaded for " .. _VERSION)
+else
+ dbg_writeln(COLOR_YELLOW .. "debugger.lua: " .. COLOR_RESET .. "Not tested against " .. _VERSION)
+ dbg_writeln "Please send me feedback!"
+end
+
+return dbg
diff --git a/.config/awesome/quarrel/delegate.lua b/.config/awesome/quarrel/delegate.lua
index 54db786..3ea6d75 100644
--- a/.config/awesome/quarrel/delegate.lua
+++ b/.config/awesome/quarrel/delegate.lua
@@ -1,8 +1,10 @@
+local M = {}
+
--- Capture `fn`'s upvalues and pass to `delegate`
---@param delegate fun(env: table<string, any>, _: ...): ...
---@param fn function
---@return fun(...): ...
-return function(delegate, fn)
+function M.new(delegate, fn)
local upvalues = {}
for i = 1, debug.getinfo(fn, "u").nups do
local name, value = debug.getupvalue(fn, i)
@@ -12,3 +14,9 @@ return function(delegate, fn)
return delegate(upvalues, ...)
end
end
+
+return setmetatable(M, {
+ __call = function(_, ...)
+ return M.new(...)
+ end,
+})
diff --git a/.config/awesome/quarrel/fs.lua b/.config/awesome/quarrel/fs.lua
index 502f189..fa3fc21 100644
--- a/.config/awesome/quarrel/fs.lua
+++ b/.config/awesome/quarrel/fs.lua
@@ -1,24 +1,35 @@
+local gdebug = require "gears.debug"
local GFile = require("lgi").Gio.File
---@class QuarrelFs
-local qfs = {}
+local M = {}
--- Read a file with the specified format (or "a") and close the file
---@param path string
----@param format openmode
+---@param format? openmode
---@return any
-function qfs.read(path, format)
+function M.read(path, format)
local f = assert(io.open(path, "r"))
local c = f:read(format or "a")
f:close()
return c
end
+--- Write a file with the specified format (or "a") and close the file
+---@param path string
+---@param format? openmode
+---@return any
+function M.write(content, path, format)
+ local f = assert(io.open(path, format or "w+"))
+ f:write(content)
+ f:close()
+end
+
--- List files in a directory
---@param path string
---@param absolute boolean?
---@return table
-function qfs.ls_files(path, absolute)
+function M.ls_files(path, absolute)
local files = GFile.new_for_path(path):enumerate_children("standard::*", 0)
local files_filtered = {}
@@ -41,4 +52,4 @@ function qfs.ls_files(path, absolute)
return files_filtered
end
-return qfs
+return M
diff --git a/.config/awesome/quarrel/iconset.lua b/.config/awesome/quarrel/iconset.lua
new file mode 100644
index 0000000..43b7104
--- /dev/null
+++ b/.config/awesome/quarrel/iconset.lua
@@ -0,0 +1,21 @@
+local gstring = require "gears.string"
+
+local M = {}
+
+--- Generate a shim to allow indexing an iconset as a Lua table
+---@param path string
+---@return table
+function M.new(path)
+ return setmetatable({}, {
+ ---@param icon string
+ __index = function(self, icon)
+ return path .. (gstring.endswith(path, "/") and "" or "/") .. icon:gsub("_", "-") .. ".svg"
+ end,
+ })
+end
+
+return setmetatable(M, {
+ __call = function(_, ...)
+ return M.new(...)
+ end,
+})
diff --git a/.config/awesome/quarrel/init.lua b/.config/awesome/quarrel/init.lua
index 96f7f5b..5617c1a 100644
--- a/.config/awesome/quarrel/init.lua
+++ b/.config/awesome/quarrel/init.lua
@@ -9,14 +9,4 @@ function quarrel.debug(message)
n { message = tostring(message) }
end
---- Check if there was a restart
----@return boolean
-function quarrel.is_restart()
- awesome.register_xproperty("is_restart", "boolean")
- local restart_detected = awesome.get_xproperty "is_restart" ~= nil
- awesome.set_xproperty("is_restart", true)
-
- return restart_detected
-end
-
return quarrel
diff --git a/.config/awesome/quarrel/lua-rust.tar.gz b/.config/awesome/quarrel/lua-rust.tar.gz
deleted file mode 100644
index 5ff33fd..0000000
--- a/.config/awesome/quarrel/lua-rust.tar.gz
+++ /dev/null
Binary files differ
diff --git a/.config/awesome/quarrel/markup.lua b/.config/awesome/quarrel/markup.lua
index d206530..f89368c 100644
--- a/.config/awesome/quarrel/markup.lua
+++ b/.config/awesome/quarrel/markup.lua
@@ -1,8 +1,10 @@
+local M = {}
+
--- Apply markup to a file
---@param content string
---@param args { bold: boolean, italic: boolean, fg: string, bg: string }
---@return string
-return function(content, args)
+function M.convert(content, args)
args = args or {}
if args.bold then
content = "<b>" .. content .. "</b>"
@@ -28,3 +30,9 @@ return function(content, args)
return content
end
+
+return setmetatable(M, {
+ __call = function(_, ...)
+ return M.convert(...)
+ end,
+})
diff --git a/.config/awesome/quarrel/math/consts.lua b/.config/awesome/quarrel/math/consts.lua
new file mode 100644
index 0000000..f18a093
--- /dev/null
+++ b/.config/awesome/quarrel/math/consts.lua
@@ -0,0 +1,5 @@
+local C = {}
+
+C.EPSILON = 2 ^ -52
+
+return C
diff --git a/.config/awesome/quarrel/math.lua b/.config/awesome/quarrel/math/init.lua
index b16547b..d437654 100644
--- a/.config/awesome/quarrel/math.lua
+++ b/.config/awesome/quarrel/math/init.lua
@@ -1,11 +1,9 @@
+local M = require "quarrel.math.consts"
local gmath = require "gears.math"
----@class QuarrelMath
-local qmath = {}
-
-- TODO: Finish documenting these functions
-function qmath.step_value(value, steps)
+function M.step_value(value, steps)
if value > steps[#steps - 1][1] then
return steps[#steps - 1][2]
end
@@ -16,16 +14,16 @@ function qmath.step_value(value, steps)
end
end
-function qmath.translate_range(value, in_min, in_max, out_min, out_max)
+function M.translate_range(value, in_min, in_max, out_min, out_max)
return out_min + ((out_max - out_min) / (in_max - in_min)) * (value - in_min)
end
-function qmath.percentage(value, max)
+function M.percentage(value, max)
return gmath.round(value / max * 100)
end
-function qmath.clamp(value, min, max)
+function M.clamp(value, min, max)
return math.max(math.min(value, max), min)
end
-return qmath
+return M
diff --git a/.config/awesome/quarrel/native/Cargo.toml b/.config/awesome/quarrel/native/Cargo.toml
index 8d56c9f..2551507 100644
--- a/.config/awesome/quarrel/native/Cargo.toml
+++ b/.config/awesome/quarrel/native/Cargo.toml
@@ -8,22 +8,21 @@ edition = "2021"
[dependencies]
freedesktop_entry_parser = "1.3.0"
cpc = "1.9.1"
-mlua = { version = "0.8.7", features = [ "module", "lua54", "serialize" ] }
+mlua = { version = "0.10.3", features = [ "module", "lua54", "serialize" ] }
rayon = "1.6.1"
serde = { version = "1.0.152", features = [ "derive" ] }
url = "2.3.1"
-rodio = "0.17.1"
-nix = "0.26.2"
+rodio = "0.20.1"
+nix = { version = "0.29.0", features = [ "ioctl", "socket" ] }
chrono = "0.4.24"
-itertools = "0.10.5"
+itertools = "0.14.0"
html-escape = "0.2.13"
-mpd = { git = "https://github.com/kstep/rust-mpd", features = [ "serde" ], version = "0.1.0" }
-cairo-rs = { git = "https://github.com/gtk-rs/gtk-rs-core.git", version = "0.18.0" }
-gdk-pixbuf = { git = "https://github.com/gtk-rs/gtk-rs-core.git", version = "0.18.0" }
-symphonia = "0.5.3"
-dirs = "5.0.1"
+dirs = "6.0.0"
once_cell = "1.18.0"
-
+ureq = "3.0.9"
+anyhow = { version = "1.0.97", features = ["backtrace"] }
+mdrop = { git = "https://github.com/frahz/mdrop" }
+sysinfo = "0.35.0"
[lib]
crate-type = ["cdylib"]
diff --git a/.config/awesome/quarrel/native/init.lua b/.config/awesome/quarrel/native/init.lua
index e5d5aab..6a823ba 100644
--- a/.config/awesome/quarrel/native/init.lua
+++ b/.config/awesome/quarrel/native/init.lua
@@ -26,16 +26,12 @@ package.cpath = package.cpath .. ";" .. cfg .. "quarrel/native/lib?.so"
---@field decode_html fun(input: string): string
---@field open_file fun(path: string): FileHandle
----@class Mpd
----@field init
-
---@class Net
---@field get_essid fun(): string
---@class QuarrelNative
---@field lenses Lenses
---@field util Util
----@field mpd Mpd
---@field net Net
local qnative = require "qnative"
diff --git a/.config/awesome/quarrel/native/src/http.rs b/.config/awesome/quarrel/native/src/http.rs
new file mode 100644
index 0000000..e995556
--- /dev/null
+++ b/.config/awesome/quarrel/native/src/http.rs
@@ -0,0 +1,22 @@
+use ureq::{Agent, agent};
+use std::{sync::LazyLock, thread};
+use mlua::prelude::*;
+
+static AGENT: LazyLock<Agent> = LazyLock::new(|| agent());
+
+struct Stream(ureq::Body);
+
+pub fn get(_: &Lua, url: String, callback: LuaFunction, err_callback: LuaFunction) -> LuaResult<()> {
+ thread::spawn(|| {
+ match AGENT.get(url).call() {
+ Ok(body) => {
+
+ },
+ Err(err) => {
+ err_callback.call(err.to_string());
+ }
+ }
+ // callback.call::<>()
+ });
+ Ok(())
+}
diff --git a/.config/awesome/quarrel/native/src/lenses/application.rs b/.config/awesome/quarrel/native/src/lenses/application.rs
index 72aba8d..89b7bb4 100644
--- a/.config/awesome/quarrel/native/src/lenses/application.rs
+++ b/.config/awesome/quarrel/native/src/lenses/application.rs
@@ -1,6 +1,5 @@
use std::{
- fs::read_dir,
- path::PathBuf,
+ fs::read_dir, path::PathBuf
};
use freedesktop_entry_parser as fd;
@@ -8,12 +7,54 @@ use mlua::prelude::*;
use rayon::prelude::*;
use url::Url;
-use crate::lenses::entry::{
- entries_to_lua_table,
+use crate::lenses::{
Entry,
+ Lense,
+ Cache
};
-fn parse_entry(entry: &fd::Entry, path: &PathBuf) -> Result<Entry, ()> {
+#[derive(Default)]
+pub struct Application(pub Cache);
+
+impl Lense for Application {
+ const NAME: &str = "Application";
+
+ fn get_cache(&self) -> &Cache {
+ &self.0
+ }
+
+ fn set_cache(&mut self, cache: Cache) {
+ self.0 = cache;
+ }
+
+ fn query(_: &Lua, input: String) -> Result<Vec<Entry>, anyhow::Error> {
+ let applications_dir = "/usr/share/applications";
+ let entries = read_dir(applications_dir)?
+ .map(|result| result.map(|e| e.path()))
+ .collect::<Result<Vec<_>, std::io::Error>>()?;
+
+ let parsed_entries: Vec<Entry> = entries
+ .into_par_iter()
+ .filter(|path| path.extension().is_some_and(|ext| ext == "desktop"))
+ .filter_map(|path| {
+ parse_entry(path).ok().flatten()
+ })
+ .collect();
+
+ Ok(
+ parsed_entries
+ .into_iter()
+ .filter(|entry| entry.message.to_lowercase().contains(&input.to_lowercase()))
+ .collect(),
+ )
+ }
+}
+
+fn parse_entry(path: PathBuf) -> Result<Option<Entry>, ()> {
+ let Ok(entry) = fd::parse_entry(&path) else {
+ return Err(())
+ };
+
let section = entry.section("Desktop Entry");
let name = section.attr("Name").ok_or(())?.to_string();
@@ -38,14 +79,14 @@ fn parse_entry(entry: &fd::Entry, path: &PathBuf) -> Result<Entry, ()> {
}
}
'c' => new_exec.replace_range(index..index + 2, &name),
- 'k' => new_exec.replace_range(index..index + 2, Url::from_file_path(path)?.as_str()),
+ 'k' => new_exec.replace_range(index..index + 2, Url::from_file_path(&path)?.as_str()),
'f' | 'u' | 'v' | 'm' | 'd' | 'n' => new_exec.replace_range(index..index + 2, ""),
_ => continue,
}
}
- Ok(Entry {
+ Ok(Some(Entry {
message: name,
exec: Some((
new_exec,
@@ -55,33 +96,5 @@ fn parse_entry(entry: &fd::Entry, path: &PathBuf) -> Result<Entry, ()> {
.parse()
.map_err(drop)?,
)),
- provider: "Application".to_string(),
- })
-}
-
-pub fn query(lua: &Lua, input: String) -> LuaResult<LuaTable> {
- let applications_dir = "/usr/share/applications";
- let entries = read_dir(applications_dir)?
- .map(|result| result.map(|e| e.path()))
- .collect::<Result<Vec<_>, std::io::Error>>()?;
-
- let parsed_entries: Vec<Entry> = entries
- .into_par_iter()
- .filter(|path| matches!(path.extension(), Some(ext) if ext == "desktop"))
- .filter_map(|path| {
- let Ok(entry) = fd::parse_entry(&path) else {
- return None
- };
-
- parse_entry(&entry, &path).ok()
- })
- .collect();
-
- Ok(entries_to_lua_table(
- parsed_entries
- .into_iter()
- .filter(|entry| entry.message.to_lowercase().contains(&input.to_lowercase()))
- .collect(),
- lua,
- ))
+ }))
}
diff --git a/.config/awesome/quarrel/native/src/lenses/calculator.rs b/.config/awesome/quarrel/native/src/lenses/calculator.rs
index 07f1ee2..640bdeb 100644
--- a/.config/awesome/quarrel/native/src/lenses/calculator.rs
+++ b/.config/awesome/quarrel/native/src/lenses/calculator.rs
@@ -5,25 +5,36 @@ use cpc::{
};
use mlua::prelude::*;
-use crate::lenses::entry::{
- entries_to_lua_table,
+use crate::lenses::{
Entry,
+ Cache,
+ Lense
};
-pub fn query(lua: &Lua, input: String) -> LuaResult<LuaTable> {
- let result = match eval(input.trim(), true, Unit::Celsius, false) {
- Ok(result) => {
- format!("{result}")
- }
- Err(_) => return lua.create_table(),
- };
-
- Ok(entries_to_lua_table(
- vec![Entry {
- message: result,
- exec: None,
- provider: "Calculator".to_string(),
- }],
- lua,
- ))
+pub struct Calculator;
+
+impl Lense for Calculator {
+ const NAME: &str = "Calculator";
+
+ fn get_cache(&self) -> &Cache {
+ &Cache::Stale
+ }
+
+ fn set_cache(&mut self, _: Cache) {}
+
+ fn query(_: &Lua, input: String) -> Result<Vec<Entry>, anyhow::Error> {
+ let result = match eval(input.trim(), true, Unit::Celsius, false) {
+ Ok(result) => {
+ format!("{result}")
+ }
+ Err(err) => { return Err(anyhow::anyhow!(err)); },
+ };
+
+ Ok(vec![Entry {
+ message: result,
+ exec: None,
+ }; 1]
+ )
+ }
}
+
diff --git a/.config/awesome/quarrel/native/src/lenses/entry.rs b/.config/awesome/quarrel/native/src/lenses/entry.rs
deleted file mode 100644
index c7ac09e..0000000
--- a/.config/awesome/quarrel/native/src/lenses/entry.rs
+++ /dev/null
@@ -1,19 +0,0 @@
-use mlua::{
- prelude::*,
- LuaSerdeExt,
-};
-use serde::Serialize;
-
-#[derive(Serialize, Clone)]
-pub struct Entry {
- pub message: String,
- pub exec: Option<(String, bool)>,
- pub provider: String,
-}
-
-pub fn entries_to_lua_table(entries: Vec<Entry>, lua: &Lua) -> LuaTable {
- match lua.to_value(&entries).unwrap() {
- LuaValue::Table(t) => t,
- _ => unreachable!(),
- }
-}
diff --git a/.config/awesome/quarrel/native/src/lenses/mod.rs b/.config/awesome/quarrel/native/src/lenses/mod.rs
index d0db6f7..6eb5b58 100644
--- a/.config/awesome/quarrel/native/src/lenses/mod.rs
+++ b/.config/awesome/quarrel/native/src/lenses/mod.rs
@@ -1,3 +1,79 @@
pub mod application;
pub mod calculator;
-pub mod entry;
+
+use mlua::{
+ prelude::*,
+ LuaSerdeExt,
+};
+use serde::{Serialize, Deserialize};
+
+#[derive(Deserialize, Serialize, Clone)]
+pub struct Entry {
+ pub message: String,
+ pub exec: Option<(String, bool)>,
+}
+
+impl IntoLua for Entry {
+ fn into_lua(self, lua: &Lua) -> LuaResult<LuaValue> {
+ return lua.to_value(&self)
+ }
+}
+
+impl FromLua for Entry {
+ fn from_lua(value: LuaValue, lua: &Lua) -> LuaResult<Self> {
+ return lua.from_value(value);
+ }
+}
+
+#[derive(Default)]
+pub enum Cache {
+ Valid(Vec<Entry>),
+ #[default]
+ Stale
+}
+
+pub struct _Lense<T: Lense>(pub T);
+
+pub trait Lense {
+ const NAME: &'static str;
+
+ fn set_cache(&mut self, cache: Cache);
+ fn get_cache(&self) -> &Cache;
+
+ fn query(lua: &Lua, input: String) -> Result<Vec<Entry>, anyhow::Error>;
+}
+
+impl<T: Lense + 'static> LuaUserData for _Lense<T> {
+ fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
+ // fields.add_field_method_get("cache", |lua, this| {
+ // let cache = this.0.get_cache();
+ // match cache {
+ // Cache::Valid(cache) => Ok(cache.clone().into_lua(lua)?),
+ // Cache::Stale => Ok(LuaNil)
+ // }
+ // });
+
+ // fields.add_field_method_set("cache", |_, this, cache: Vec<Entry>| {
+ // Ok(this.0.set_cache(Cache::Valid(cache)))
+ // });
+
+ fields.add_field_method_get("stale", |_, this| Ok(matches!(this.0.get_cache(), Cache::Stale)));
+
+ fields.add_field("name", T::NAME);
+ }
+
+ fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
+ methods.add_method_mut("query", |lua, this, input: String| {
+ return Ok(match this.0.get_cache() {
+ Cache::Valid(entries) => entries.clone(),
+ Cache::Stale => {
+ let entries = T::query(lua, input).map_err(LuaError::external)?;
+ this.0.set_cache(Cache::Valid(entries.clone()));
+ entries
+ }
+ });
+ });
+
+ methods.add_method_mut("mark_stale", |_, this, _: ()| Ok(this.0.set_cache(Cache::Stale)) );
+ }
+}
diff --git a/.config/awesome/quarrel/native/src/lib.rs b/.config/awesome/quarrel/native/src/lib.rs
index 472313e..4f1780d 100644
--- a/.config/awesome/quarrel/native/src/lib.rs
+++ b/.config/awesome/quarrel/native/src/lib.rs
@@ -1,29 +1,29 @@
mod lenses;
-mod mpd;
mod net;
+// mod http;
mod util;
+mod moondrop;
use mlua::prelude::*;
+use lenses::{ _Lense };
#[mlua::lua_module]
fn qnative(lua: &Lua) -> LuaResult<LuaTable> {
let lenses = lua.create_table()?;
- lenses.set("1", lua.create_function(lenses::calculator::query)?)?;
- lenses.set("2", lua.create_function(lenses::application::query)?)?;
+ lenses.push(_Lense(lenses::calculator::Calculator))?;
+ lenses.push(_Lense(lenses::application::Application::default()))?;
+ // lenses.set("1", lua.create_function(lenses::calculator::Application::query)?)?;
+ // lenses.set("2", lua.create_function(lenses::application::Application::query)?)?;
let util = lua.create_table()?;
util.set("decode_html", lua.create_function(util::decode_html)?)?;
util.set("open_file", lua.create_function(util::FileHandle::new)?)?;
- let mpd = lua.create_table()?;
- mpd.set("init", lua.create_function(mpd::init)?)?;
-
let net = lua.create_table()?;
net.set("get_essid", lua.create_function(net::get_first_essid)?)?;
let exports = lua.create_table()?;
exports.set("lenses", lenses)?;
- exports.set("mpd", mpd)?;
exports.set("net", net)?;
exports.set("util", util)?;
diff --git a/.config/awesome/quarrel/native/src/moondrop.rs b/.config/awesome/quarrel/native/src/moondrop.rs
new file mode 100644
index 0000000..d416ef6
--- /dev/null
+++ b/.config/awesome/quarrel/native/src/moondrop.rs
@@ -0,0 +1,3 @@
+use mdrop::Moondrop;
+
+
diff --git a/.config/awesome/quarrel/native/src/mpd.rs b/.config/awesome/quarrel/native/src/mpd.rs
deleted file mode 100644
index 08c9e42..0000000
--- a/.config/awesome/quarrel/native/src/mpd.rs
+++ /dev/null
@@ -1,142 +0,0 @@
-use std::{
- ffi::c_void,
- fs::File,
- net::TcpStream,
- path::PathBuf,
- sync::mpsc::channel,
-};
-
-use dirs::home_dir;
-use gdk_pixbuf::{
- ffi::GdkPixbuf,
- glib::translate::IntoGlibPtr,
- traits::PixbufLoaderExt,
- Pixbuf,
- PixbufLoader,
-};
-use mlua::{
- prelude::*,
- LuaSerdeExt,
-};
-use mpd::Client;
-use once_cell::sync::Lazy;
-use symphonia::{
- core::{
- formats::FormatOptions,
- io::{
- MediaSourceStream,
- MediaSourceStreamOptions,
- },
- meta::MetadataOptions,
- probe::Hint,
- },
- default::get_probe,
-};
-
-static MPD_MUSIC_PATH: Lazy<PathBuf> = Lazy::new(|| {
- [
- home_dir().expect("home directory should be set"),
- "Music".into(),
- ]
- .iter()
- .collect()
-});
-
-static COVER_FORMATS: [&str; 2] = ["png", "jpg"];
-
-pub struct Connection(Client<TcpStream>);
-
-impl LuaUserData for Connection {
- fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
- methods.add_method_mut("status", |lua, this: &mut Connection, (): ()| {
- lua.to_value(&this.0.status().map_err(LuaError::external)?)
- });
-
- methods.add_method_mut("song", |lua, this: &mut Connection, (): ()| {
- Ok(
- if let Some(song) = this.0.currentsong().map_err(LuaError::external)? {
- lua.to_value(&song)?
- } else {
- LuaNil
- },
- )
- });
-
- methods.add_method(
- "get_cover_pixbuf",
- |_, _: &Connection, file: String| {
- let song_path = MPD_MUSIC_PATH.join(file);
- let mut has_external_cover = false;
- let mut cover_path = PathBuf::new();
-
- for format in COVER_FORMATS {
- let cover = song_path
- .parent()
- .unwrap()
- .to_owned()
- .join(format!("cover.{}", format));
- if cover.exists() {
- has_external_cover = cover.exists();
- cover_path = cover;
- break;
- }
- }
-
- let mss = MediaSourceStream::new(
- Box::new(File::open(song_path)?),
- MediaSourceStreamOptions::default(),
- );
-
- let mut probed = get_probe()
- .format(
- &Hint::default(),
- mss,
- &FormatOptions::default(),
- &MetadataOptions::default(),
- )
- .map_err(LuaError::external)?;
-
- let visuals;
-
- if let Some(metadata) = probed.format.metadata().skip_to_latest() {
- visuals = metadata.visuals();
- if visuals.is_empty() && has_external_cover {
- let pixbuf = Pixbuf::from_file(cover_path).map_err(LuaError::external)?;
-
- return Ok((
- Some(LuaLightUserData(unsafe {
- <Pixbuf as IntoGlibPtr<*mut GdkPixbuf>>::into_glib_ptr(pixbuf)
- .cast::<c_void>()
- })),
- Some(true),
- ));
- }
-
- let loader = PixbufLoader::new();
- loader
- .write(visuals.first().unwrap().data.as_ref())
- .map_err(LuaError::external)?;
- loader.close().map_err(LuaError::external)?;
-
- return Ok((
- Some(LuaLightUserData(unsafe {
- <Pixbuf as IntoGlibPtr<*mut GdkPixbuf>>::into_glib_ptr(
- loader.pixbuf().expect("Pixbuf should be initialized"),
- )
- .cast::<c_void>()
- })),
- Some(false),
- ));
- }
-
- Ok((None, None))
- },
- );
- }
-}
-
-pub fn init(_: &Lua, _: ()) -> LuaResult<Connection> {
- Ok(Connection(
- Client::connect("localhost:6600").map_err(LuaError::external)?,
- ))
-}
diff --git a/.config/awesome/quarrel/native/src/net/mod.rs b/.config/awesome/quarrel/native/src/net/mod.rs
index 96c853e..dbd87b9 100644
--- a/.config/awesome/quarrel/native/src/net/mod.rs
+++ b/.config/awesome/quarrel/native/src/net/mod.rs
@@ -9,7 +9,7 @@ use std::{
c_void,
},
mem::size_of,
- os::fd::RawFd,
+ os::fd::{IntoRawFd, RawFd},
};
use mlua::prelude::*;
@@ -108,7 +108,7 @@ fn get_first_socket() -> LuaResult<RawFd> {
for family in families {
if let Ok(socket) = open_socket(family, SockType::Datagram, SockFlag::empty(), None) {
- return Ok(socket);
+ return Ok(socket.into_raw_fd());
}
}
diff --git a/.config/awesome/quarrel/native/src/util.rs b/.config/awesome/quarrel/native/src/util.rs
index 85a2574..41096d8 100644
--- a/.config/awesome/quarrel/native/src/util.rs
+++ b/.config/awesome/quarrel/native/src/util.rs
@@ -1,7 +1,6 @@
use std::{
cell::RefCell,
fs::File,
- fs::OpenOptions,
io::{
Read,
Seek,
@@ -49,7 +48,7 @@ enum StringOrNumber {
pub struct FileHandle(Rc<RefCell<File>>);
impl LuaUserData for FileHandle {
- fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
+ fn add_methods<'lua, M: LuaUserDataMethods<Self>>(methods: &mut M) {
methods.add_method("read", |lua, this: &FileHandle, mode: String| {
let content = this.read()?;
Ok(lua.to_value(&match ReadMode::from_str(&mode).unwrap() {
diff --git a/.config/awesome/quarrel/persistent.lua b/.config/awesome/quarrel/persistent.lua
new file mode 100644
index 0000000..2b15c89
--- /dev/null
+++ b/.config/awesome/quarrel/persistent.lua
@@ -0,0 +1,62 @@
+local gdebug = require "gears.debug"
+local qfs = require "quarrel.fs"
+local qjson = require "quarrel.json"
+
+awesome.register_xproperty("is_restart", "boolean")
+
+---@alias QPersistentValue string|number|table|nil|boolean
+
+---@class QuarrelPersistent
+local qpersistent = {}
+
+---@type table<string, QPersistentValue>
+local inner = {}
+
+--- Store a key-value pair in a persistent store
+---@param key string
+---@param value QPersistentValue
+function qpersistent.store(key, value)
+ inner[key] = value
+end
+
+--- Get a key-value pair in a persistent store
+---@param key string
+---@return QPersistentValue
+function qpersistent.get(key)
+ return inner[key]
+end
+
+--- Check if there was a restart
+---@return boolean
+function qpersistent.is_restart()
+ return awesome.get_xproperty "is_restart" ~= nil
+end
+
+do
+ local ok, content = pcall(qfs.read, "/tmp/qpersistent-store")
+
+ if not ok and not qpersistent.is_restart() then
+ gdebug.print_error "failed to read persistent store"
+ inner = {}
+ else
+ inner = qjson.decode(content)
+ end
+end
+
+awesome.connect_signal("exit", function(restart)
+ if not restart then
+ return
+ end -- we probably errored
+
+ local ok = pcall(qfs.write, qjson.encode(inner), "/tmp/qpersistent-store")
+ if not ok then
+ gdebug.print_error "failed to open persistent store"
+ return
+ end
+
+ if not qpersistent.is_restart() then
+ awesome.set_xproperty("is_restart", true)
+ end
+end)
+
+return qpersistent
diff --git a/.config/awesome/quarrel/store.lua b/.config/awesome/quarrel/store.lua
deleted file mode 100644
index 9422c21..0000000
--- a/.config/awesome/quarrel/store.lua
+++ /dev/null
@@ -1,4 +0,0 @@
----@type table
-local qstore = {}
-
-return qstore
diff --git a/.config/awesome/quarrel/ui.lua b/.config/awesome/quarrel/ui.lua
deleted file mode 100644
index 1db4a70..0000000
--- a/.config/awesome/quarrel/ui.lua
+++ /dev/null
@@ -1,145 +0,0 @@
-local awful = require "awful"
-local gtable = require "gears.table"
-local qbind = require "quarrel.bind"
-local qvars = require "quarrel.vars"
-local rtimed = require "lib.rubato.timed"
-local wibox = require "wibox"
-
----@class QuarrelUi
-local qui = {}
-
---- Return qvars.text_font with size scaled by factor
----@param factor number
----@return string
----@see QuarrelVars.text_font
-function qui.font(factor)
- return qvars.text_font .. " " .. qvars.font_size * (factor or 1)
-end
-
---- Inject background widget styling into target
----@param target table
----@return table
-function qui.styled(target)
- return gtable.crush({
- bg = qvars.colors.bg,
- border_color = qvars.colors.bright.black,
- border_width = qvars.border_width,
- shape = qvars.shape,
- }, target)
-end
-
---- Generate a styled popup
----@param target table
----@return table
-function qui.popup(target)
- target.widget = {
- widget = wibox.container.margin,
- margins = qvars.big_padding,
- target.widget,
- }
-
- return awful.popup(qui.styled(target))
-end
-
---- Generate svg recolor string
----@param color string
----@return string
-function qui.recolor(color)
- return "svg{fill:" .. color .. "}"
-end
-
---- Generate icon widget
----@param args table
----@return table
-function qui.icon(args)
- return gtable.crush({
- widget = wibox.widget.imagebox,
- image = args.icon,
- forced_width = qvars.char_height,
- forced_height = qvars.char_height,
- stylesheet = qui.recolor(args.color or qvars.colors.fg),
- }, args.widget or {})
-end
-
---- Generate button widget
----@param args table
----@return table
-function qui.button(args)
- args.press = args.press or function(_) end
- local widget = wibox.widget(gtable.crush({
- widget = wibox.widget.imagebox,
- image = args.image,
- forced_height = qvars.char_height,
- forced_width = qvars.char_height,
- stylesheet = qui.recolor(qvars.colors.fg),
- press = args.press,
- }, args.widget or {}))
-
- widget.buttons = {
- qbind:new {
- triggers = qvars.btns.left,
- press = function()
- widget:press()
- end,
- hidden = true,
- },
- }
-
- return widget
-end
-
---- Generate toggle widget
----@param args table
----@return table
-function qui.toggle(args)
- args.press = args.press or function(_) end
- local widget = qui.button {
- widget = gtable.crush({
- toggled = false,
- silent_press = function(self, state)
- if state then
- self.toggled = state
- else
- self.toggled = not self.toggled
- end
-
- if self.toggled then
- self.image = args.on
- else
- self.image = args.off
- end
- end,
- }, args.widget or {}),
- image = args.off,
- press = function(self)
- if not args.manual then
- self:silent_press()
- end
- args.press(self)
- end,
- }
-
- return widget
-end
-
----@param widget wibox.widget.base
----@param cursor string
-function qui.hoverable(widget, cursor)
- local hovering = false
-
- widget:connect_signal("mouse::enter", function()
- local w = mouse.current_wibox
- if w then
- w.cursor = cursor
- end
- end)
-
- widget:connect_signal("mouse::leave", function()
- local w = mouse.current_wibox
- if w then
- w.cursor = "left_ptr"
- end
- end)
-end
-
-return qui
diff --git a/.config/awesome/quarrel/ui/consts.lua b/.config/awesome/quarrel/ui/consts.lua
new file mode 100644
index 0000000..e6e12d0
--- /dev/null
+++ b/.config/awesome/quarrel/ui/consts.lua
@@ -0,0 +1,24 @@
+local awful = require "awful"
+local qconst = require "quarrel.const"
+local wibox = require "wibox"
+
+local C = {}
+
+C.BORDER_WIDTH = 1
+C.BORDER_RADIUS = 4
+C.PADDING = 4
+C.BIG_PADDING = 8
+C.TEXT_FONT = "Iosevka Comfy Regular"
+C.FONT_SIZE = 9
+
+local char_width, char_height = wibox
+ .widget({
+ widget = wibox.widget.textbox,
+ text = "a",
+ })
+ :get_preferred_size_at_dpi(awful.screen.focused().dpi)
+
+C.CHAR_HEIGHT = char_height
+C.CHAR_WIDTH = char_width
+
+return C
diff --git a/.config/awesome/quarrel/ui/init.lua b/.config/awesome/quarrel/ui/init.lua
new file mode 100644
index 0000000..5acaa51
--- /dev/null
+++ b/.config/awesome/quarrel/ui/init.lua
@@ -0,0 +1,167 @@
+local M = require "quarrel.ui.consts"
+local awful = require "awful"
+local gshape = require "gears.shape"
+local gtable = require "gears.table"
+local qbind = require "quarrel.bind"
+local qcolor = require "quarrel.color"
+local wibox = require "wibox"
+
+--- Clip Cairo context
+---@param cr cairo_surface Cairo surface
+---@param w integer Widget width
+---@param h integer Widget height
+---@return nil
+function M.shape(cr, w, h)
+ -- gears.shape.rounded_rect(cr, w, h, dpi(4))
+ gshape.rounded_rect(cr, w, h, M.BORDER_RADIUS)
+ -- gshape.rectangle(cr, w, h)
+end
+
+--- Returns qvars.text_font with size scaled by factor
+---@param factor number?
+---@return string
+function M.font(factor)
+ return M.TEXT_FONT .. " " .. M.FONT_SIZE * (factor or 1)
+end
+
+--- Injects background widget styling into target
+---@param target table
+---@return table
+function M.styled(target)
+ return gtable.crush({
+ bg = qcolor.palette.bg(),
+ border_color = qcolor.palette.border(),
+ border_width = M.BORDER_WIDTH,
+ shape = M.shape,
+ }, target)
+end
+
+--- Generates a styled popup
+---@param target table
+---@return table
+function M.popup(target, debug)
+ target.widget = {
+ widget = wibox.container.margin,
+ margins = M.BIG_PADDING,
+ target.widget,
+ }
+
+ return awful.popup(M.styled(target))
+end
+
+--- Generates svg recolor string
+---@param color string
+---@return string
+function M.recolor(color)
+ return "svg{fill:" .. color .. "}"
+end
+
+--- Generates icon widget
+---@param args table
+---@return table
+function M.icon(args)
+ return wibox.widget(gtable.crush({
+ widget = wibox.widget.imagebox,
+ image = args.icon,
+ forced_width = (not args.unsized) and M.CHAR_HEIGHT,
+ forced_height = (not args.unsized) and M.CHAR_HEIGHT,
+ stylesheet = M.recolor(args.color or qcolor.palette.fg()),
+ }, args.widget or {}))
+end
+
+--- Generates button widget
+---@param args table
+---@return table
+function M.button(args)
+ args.press = args.press or function(_) end
+ local widget = wibox.widget(gtable.crush({
+ widget = wibox.widget.imagebox,
+ image = args.icon,
+ forced_height = M.CHAR_HEIGHT,
+ forced_width = M.CHAR_HEIGHT,
+ stylesheet = M.recolor(qcolor.palette.fg()),
+ press = args.press,
+ }, args.widget or {}))
+
+ widget.buttons = {
+ qbind {
+ triggers = qbind.btns.left,
+ press = function()
+ widget:press()
+ end,
+ hidden = true,
+ },
+ }
+
+ return widget
+end
+
+--- Generates toggle widget
+---@param args table
+---@return table
+function M.toggle(args)
+ args.press = args.press or function(_) end
+ local widget = M.button {
+ widget = gtable.crush({
+ toggled = false,
+ silent_press = function(self, state)
+ if state then
+ self.toggled = state
+ else
+ self.toggled = not self.toggled
+ end
+
+ if self.toggled then
+ self.image = args.on
+ else
+ self.image = args.off
+ end
+ end,
+ }, args.widget or {}),
+ icon = args.off,
+ press = function(self)
+ if not args.manual then
+ self:silent_press()
+ end
+ args.press(self)
+ end,
+ }
+
+ return widget
+end
+
+---@param widget wibox.widget.base
+---@param cursor string
+---@return wibox.widget.base
+function M.hoverable(widget, cursor)
+ local last_wibox
+
+ widget:connect_signal("mouse::enter", function()
+ local w = mouse.current_wibox
+ last_wibox = w
+ if w then
+ w.cursor = cursor
+ end
+
+ if type(widget.hover) == "function" then -- on enter bind
+ widget.hover()
+ elseif type(widget.hover) == "table" and widget.hover.enter then -- { enter: fun(), leave: fun() }
+ widget.hover.enter()
+ end
+ end)
+
+ widget:connect_signal("mouse::leave", function()
+ local w = last_wibox or mouse.current_wibox
+ if w then
+ w.cursor = "left_ptr"
+ end
+
+ if type(widget.hover) == "table" and widget.hover.leave then -- { enter: fun(), leave: fun() }
+ widget.hover.leave()
+ end
+ end)
+
+ return widget
+end
+
+return M
diff --git a/.config/awesome/quarrel/vars.lua b/.config/awesome/quarrel/vars.lua
index 1983343..5c3b178 100644
--- a/.config/awesome/quarrel/vars.lua
+++ b/.config/awesome/quarrel/vars.lua
@@ -1,37 +1,38 @@
local awful = require "awful"
local btns = awful.button.names
-local gears = require "gears"
-local xresources = require "beautiful.xresources"
-local x_col = xresources.get_current_theme()
-local dpi = xresources.apply_dpi
+local gshape = require "gears.shape"
local wibox = require "wibox"
+-- local qconts = require "quarrel.consts"
+local qbezier = require "quarrel.animation.bezier"
----@class QuarrelVars
local qvars = {}
-qvars.anim_duration = 0.20
-qvars.anim_intro = qvars.anim_duration / 4
-
-qvars.notif_timeout = 3
+-- ---@param protected (fun(): table) | table
+-- function qconsts.protect(protected)
+-- ---@type table
+-- local tbl
+-- if type(protected) == "table" then
+-- tbl = protected
+-- elseif type(protected) == "function" then
+-- tbl = protected()
+-- else
+-- error("expected a table or a function that returns a table, got " .. type(protected), 2)
+-- end
+-- return setmetatable({}, {
+-- __index = tbl,
+-- __newindex = function(t, k, v)
+-- error("attempted to change constant " .. tostring(k) .. " to " .. tostring(v), 2)
+-- end
+-- })
+-- end
+
+-- qui.BORDER_WIDTH = 1
+-- qui.BORDER_RADIUS = 4
+
+-- qui.PADDING = 4
+-- qui.BIG_PADDING = 8
---- Clip Cairo context
----@param cr cairo_surface Cairo surface
----@param w integer Widget width
----@param h integer Widget height
----@return nil
-function qvars.shape(cr, w, h)
- gears.shape.rounded_rect(cr, w, h, dpi(4))
-end
-
-qvars.border_width = dpi(1.5)
-
-qvars.padding = dpi(4)
-qvars.big_padding = dpi(8)
-
--- qvars.text_font = "Fira Code Nerd Font Mono Medium"
--- qvars.text_font = "Iosevka Comfy SemiBold"
qvars.text_font = "Iosevka Comfy Regular"
--- qvars.font_size = 8
qvars.font_size = 9
qvars.font = qvars.text_font .. " " .. qvars.font_size
@@ -42,63 +43,152 @@ local char_width, char_height = wibox
})
:get_preferred_size_at_dpi(awful.screen.focused().dpi)
-qvars.char_height = char_height
-qvars.char_width = char_width
-
-qvars.bar_size = dpi(24) + qvars.big_padding * 2
-qvars.element_size = dpi(12)
-qvars.expanded_bar_size = qvars.big_padding + (qvars.big_padding * 2 + qvars.element_size * 4) * 3 + qvars.padding * 2
-
-qvars.colors = {
- fg = x_col.foreground,
- bg = x_col.background,
-
- black = x_col.color0,
- red = x_col.color1,
- green = x_col.color2,
- yellow = x_col.color3,
- blue = x_col.color4,
- pink = x_col.color5,
- cyan = x_col.color6,
- white = x_col.color7,
-
- bright = {
- black = x_col.color8,
- red = x_col.color9,
- green = x_col.color10,
- yellow = x_col.color11,
- blue = x_col.color12,
- pink = x_col.color13,
- cyan = x_col.color14,
- white = x_col.color15,
- },
-
- dim = {
- fg = "#77828c",
- bg = "#161b22",
- },
-
- transparent = "#00000000",
-}
+-- qui.CHAR_HEIGHT = char_height
+-- qui.CHAR_WIDTH = char_width
+
+--- Clip Cairo context
+---@param cr cairo_surface Cairo surface
+---@param w integer Widget width
+---@param h integer Widget height
+---@return nil
+-- function qui.shape(cr, w, h)
+-- -- gears.shape.rounded_rect(cr, w, h, dpi(4))
+-- gshape.rounded_rect(cr, w, h, qui.BORDER_RADIUS)
+-- end
+
+qvars.anim_duration = 0.30
+qvars.anim_intro = qvars.anim_duration / 4
+
+local easing_bezier = qbezier(0.2, 0, 0, 1)
+function qvars.easing(t, b, c, d)
+ local epsilon = 1000 / d
+ return c * easing_bezier:solve(t / d, epsilon) + b
+end
+
+qvars.notif_timeout = 3
+
+-- qvars.bar_size = dpi(24) + qui.BIG_PADDING * 2
+-- qvars.element_size = dpi(12)
+-- qvars.bar_size = 24 + qui.BIG_PADDING * 2
+-- qvars.element_size = 12
+-- qvars.expanded_bar_size = qui.BIG_PADDING + (qui.BIG_PADDING * 2 + qvars.element_size * 4) * 4 + qui.PADDING * 2
+
+-- local function palette(colors)
+-- return setmetatable(colors, {
+-- __call = function(self)
+-- return self[1]
+-- end,
+-- })
+-- end
+
+-- local colors = require "quarrel.color"
+-- qcolor.palette = colors.palette
+-- qcolor.palette = {
+-- fg = palette {
+-- low = "#a8afb7",
+-- "#c8ced7",
+-- high = "#e8eff8",
+-- },
+--
+-- bg = palette {
+-- lowest = "#070c11",
+-- low = "#0c1116",
+-- "#12161c",
+-- high = "#171c22",
+-- highest = "#1d2228",
+-- },
+--
+-- border = palette {
+-- "#323b46",
+-- variant = "#262f39",
+-- },
+--
+-- red = palette {
+-- "#ff928a",
+-- bright = "#ffb2a9",
+-- },
+-- orange = palette {
+-- "#ff9f6f",
+-- bright = "#ffc08e",
+-- },
+-- yellow = palette {
+-- "#ecb256",
+-- bright = "#ffd278",
+-- },
+-- green = palette {
+-- "#8bd294",
+-- bright = "#abf3b3",
+-- },
+-- cyan = palette {
+-- "#6dd3c2",
+-- bright = "#8ef4e2",
+-- },
+-- blue = palette {
+-- "#8bc3fc",
+-- bright = "#abe4ff",
+-- },
+-- purple = palette {
+-- "#c4b1f6",
+-- bright = "#e4d1ff",
+-- },
+-- pink = palette {
+-- "#e5acb4",
+-- bright = "#ffccd4",
+-- },
+--
+-- transparent = "#00000000",
+-- }
+
+-- qcolor.palette = {
+-- fg = x_col.foreground,
+-- bg = x_col.background,
+--
+-- black = x_col.color0,
+-- red = x_col.color1,
+-- green = x_col.color2,
+-- yellow = x_col.color3,
+-- blue = x_col.color4,
+-- pink = x_col.color5,
+-- cyan = x_col.color6,
+-- white = x_col.color7,
+--
+-- bright = {
+-- black = x_col.color8,
+-- red = x_col.color9,
+-- green = x_col.color10,
+-- yellow = x_col.color11,
+-- blue = x_col.color12,
+-- pink = x_col.color13,
+-- cyan = x_col.color14,
+-- white = x_col.color15,
+-- },
+--
+-- dim = {
+-- fg = "#77828c",
+-- bg = "#161b22",
+-- },
+--
+-- transparent = "#00000000",
+-- }
-- taken from https://github.com/bew/dotfiles/blob/ab9bb1935783f7a31ef777b1d7e26d53f35df864/gui/wezterm/cfg_utils.lua
-qvars.mods = setmetatable({ _SHORT_MAP = { C = "Control", S = "Shift", A = "Mod1", M = "Mod4" } }, {
- __index = function(self, key)
- local resolved_mods = {}
- for i = 1, #key do
- resolved_mods[i] = self._SHORT_MAP[key:sub(i, i)]
- end
- return resolved_mods
- end,
-})
-
----@enum buttons
-qvars.btns = {
- left = btns.LEFT,
- right = btns.RIGHT,
- middle = btns.MIDDLE,
- up = btns.SCROLL_UP,
- down = btns.SCROLL_DOWN,
-}
+-- qbind.mods = setmetatable({ _SHORT_MAP = { C = "Control", S = "Shift", A = "Mod1", M = "Mod4" } }, {
+-- __index = function(self, key)
+-- local resolved_mods = {}
+-- for i = 1, #key do
+-- resolved_mods[i] = self._SHORT_MAP[key:sub(i, i)]
+-- end
+-- return resolved_mods
+-- end,
+-- })
+--
+-- ---@enum buttons
+-- qbind.btns = {
+-- left = btns.LEFT,
+-- right = btns.RIGHT,
+-- middle = btns.MIDDLE,
+-- up = btns.SCROLL_UP,
+-- down = btns.SCROLL_DOWN,
+-- }
return qvars
diff --git a/.config/awesome/rc.lua b/.config/awesome/rc.lua
index be5b3f1..32147be 100644
--- a/.config/awesome/rc.lua
+++ b/.config/awesome/rc.lua
@@ -18,7 +18,6 @@ require "services"
require "misc"
require "ui"
-
-- require "curious"
-- local gdebug = require "gears.debug"
--
diff --git a/.config/awesome/services/backlight.lua b/.config/awesome/services/backlight.lua
index 02ed808..bb9bedc 100644
--- a/.config/awesome/services/backlight.lua
+++ b/.config/awesome/services/backlight.lua
@@ -1,10 +1,8 @@
local backlight = require("lib.lit").backlight.new("amdgpu_bl1", 5)
local gobject = require "gears.object"
-local gtimer = require "gears.timer"
-local naughty = require "naughty"
local phosphor = require "assets.phosphor"
+local qcolor = require "quarrel.color"
local qmath = require "quarrel.math"
-local qvars = require "quarrel.vars"
local backlight_wrapper = gobject {
class = {
@@ -27,7 +25,7 @@ backlight:connect_signal("brightness", function(_, brightness)
{ 255 },
})
- backlight_wrapper:emit_signal("icon", phosphor[icon_data .. "_fill"], qvars.colors.fg)
+ backlight_wrapper:emit_signal("icon", phosphor[icon_data .. "_fill"], qcolor.palette.fg())
backlight_wrapper:emit_signal("value", brightness)
end)
diff --git a/.config/awesome/services/battery.lua b/.config/awesome/services/battery.lua
index 98bacc9..d10714e 100644
--- a/.config/awesome/services/battery.lua
+++ b/.config/awesome/services/battery.lua
@@ -2,8 +2,8 @@ local UPower = require("lgi").UPowerGlib
local gobject = require "gears.object"
local gtimer = require "gears.timer"
local phosphor = require "assets.phosphor"
+local qcolor = require "quarrel.color"
local qmath = require "quarrel.math"
-local qvars = require "quarrel.vars"
local upower = assert(UPower.Client.new_full())
local inner
@@ -51,7 +51,11 @@ local function icon_handler()
{ 100 },
})
- battery:emit_signal("icon", phosphor["battery_vertical_" .. icon_data[1] .. "_fill"], qvars.colors[icon_data[2]])
+ battery:emit_signal(
+ "icon",
+ phosphor["battery_vertical_" .. icon_data[1] .. "_fill"],
+ qcolor.palette[icon_data[2]]()
+ )
end
local function level_handler()
diff --git a/.config/awesome/services/common.lua b/.config/awesome/services/common.lua
index ad1f998..9657c8a 100644
--- a/.config/awesome/services/common.lua
+++ b/.config/awesome/services/common.lua
@@ -1,12 +1,10 @@
local gfs = require "gears.filesystem"
local gtimer = require "gears.timer"
local phosphor = require "assets.phosphor"
+local qcolor = require "quarrel.color"
local qjson = require "quarrel.json"
local qmath = require "quarrel.math"
local qnative = require "quarrel.native"
-local qstore = require "quarrel.store"
-local qvars = require "quarrel.vars"
-local UPower = require("lgi").UPowerGlib
--- Register a service
---@param name string
@@ -39,9 +37,9 @@ if not gfs.file_readable "/tmp/wp_audio_status" then
assert(io.open("/tmp/wp_audio_status", "w")):write("{}"):close()
end
-qstore.audio_file = qnative.util.open_file "/tmp/wp_audio_status"
-qstore.brightness_file = qnative.util.open_file "/sys/class/backlight/amdgpu_bl1/actual_brightness"
-qstore.wifi_file = qnative.util.open_file "/proc/net/wireless"
+local audio_file = qnative.util.open_file "/tmp/wp_audio_status"
+-- qstore.brightness_file = qnative.util.open_file "/sys/class/backlight/amdgpu_bl1/actual_brightness"
+local wifi_file = qnative.util.open_file "/proc/net/wireless"
-- follows the format `service = { provider, icon_provider }`
---@class Service
@@ -53,7 +51,7 @@ local services = {
audio = {
-- volume, muted
function()
- local audio_status = qjson.decode(read(qstore.audio_file))
+ local audio_status = qjson.decode(read(audio_file))
local default_sink = audio_status["G435 Wireless Gaming Headset Analog Stereo"]
if not default_sink then
@@ -64,7 +62,7 @@ local services = {
end,
function(volume, muted)
if muted or not volume then
- return phosphor.speaker_simple_x_fill, qvars.colors.red
+ return phosphor.speaker_simple_x_fill, qcolor.palette.red()
end
local icon_data = qmath.step_value(volume, {
@@ -75,32 +73,32 @@ local services = {
{ 100 },
})
- return phosphor["speaker_simple_" .. icon_data .. "_fill"], qvars.colors.fg
- end,
- },
- brightness = {
- -- brightness
- function()
- return read(qstore.brightness_file, "n")
- end,
- function(brightness)
- local icon_data = qmath.step_value(brightness, {
- { 0, "cloud_moon" },
- { 51, "moon" },
- { 102, "sun_horizon" },
- { 153, "sun_dim" },
- { 204, "sun" },
- { 255 },
- })
-
- return phosphor[icon_data .. "_fill"], qvars.colors.fg
+ return phosphor["speaker_simple_" .. icon_data .. "_fill"], qcolor.palette.fg()
end,
},
+ -- brightness = {
+ -- -- brightness
+ -- function()
+ -- return read(qstore.brightness_file, "n")
+ -- end,
+ -- function(brightness)
+ -- local icon_data = qmath.step_value(brightness, {
+ -- { 0, "cloud_moon" },
+ -- { 51, "moon" },
+ -- { 102, "sun_horizon" },
+ -- { 153, "sun_dim" },
+ -- { 204, "sun" },
+ -- { 255 },
+ -- })
+ --
+ -- return phosphor[icon_data .. "_fill"], qcolor.palette.fg()
+ -- end,
+ -- },
wifi = {
-- essid, strength, connected
function()
- local lines = qstore.wifi_file:lines()
- qstore.wifi_file:rewind()
+ local lines = wifi_file:lines()
+ wifi_file:rewind()
if not lines[3] then
return nil, 0, false
@@ -112,7 +110,7 @@ local services = {
end,
function(_, strength, connected)
if not connected then
- return phosphor.wifi_x_fill, qvars.colors.red
+ return phosphor.wifi_x_fill, qcolor.palette.red()
end
local icon_data = qmath.step_value(strength, {
@@ -123,7 +121,7 @@ local services = {
{ 100 },
})
- return phosphor["wifi_" .. icon_data[1] .. "_fill"], qvars.colors[icon_data[2]]
+ return phosphor["wifi_" .. icon_data[1] .. "_fill"], qcolor.palette[icon_data[2]]()
end,
},
}
diff --git a/.config/awesome/services/mpris/init.lua b/.config/awesome/services/mpris/init.lua
new file mode 100644
index 0000000..3659a3a
--- /dev/null
+++ b/.config/awesome/services/mpris/init.lua
@@ -0,0 +1,100 @@
+local gobject = require "gears.object"
+local playerctl = require "services.mpris.playerctl"
+local qpersistent = require "quarrel.persistent"
+
+---@class ServiceMpris : gears.object
+---@field inner Playerctl
+---@field index number the index of the currently active player
+local M = {}
+
+---@param self ServiceMpris
+---@param player Playerctl.data
+local function update_player(self, player)
+ self:emit_signal("player::metadata", player)
+ self:emit_signal("player::position", player)
+ -- handle_position(nil, player)
+end
+
+---@param diff_player Playerctl.data
+local function recalculate_active_player(diff_player, vanished)
+ if type(diff_player) ~= "table" then
+ return
+ end
+ -- if #layout.children == 0 then
+ -- M.active_player_index = 1
+ -- update_player()
+ -- return
+ -- end
+
+ local active_player = players[M.active_player_index]
+ if not active_player then -- we're recovering from a state with no players
+ update_player(diff_player)
+ return
+ end
+
+ if diff_player.instance == active_player.instance and vanished then -- active player vanished; fall back to previous player
+ M.previous_player()
+ else -- non-active player appeared/vanished; try to find active player
+ for i, p in ipairs(playerctl:list()) do
+ if p.instance == active_player.instance then
+ M.active_player_index = i
+ update_player(p)
+ return
+ end
+ end
+
+ gdebug.print_warning(
+ "failed to find active player:\n " .. gdebug.dump_return(active_player, nil, 2):gsub("\n", "\n ")
+ )
+ M.active_player_index = 1
+ update_player(playerctl:list()[M.active_player_index])
+ end
+end
+
+function M:next_player()
+ local players = self.inner:list()
+
+ if #players == 0 then
+ return
+ elseif self.index + 1 > #players then
+ self.index = 1
+ else
+ self.index = self.index + 1
+ end
+
+ -- update_player(playerctl:list()[M.active_player_index])
+ local player = players[self.index]
+ self:emit_signal("player::metadata", player)
+ self:emit_signal("player::position", player)
+end
+
+function M:previous_player()
+ local players = self.inner:list()
+ if #players == 0 then
+ return
+ elseif self.index - 1 < #players then
+ self.index = #players
+ else
+ self.index = self.index - 1
+ end
+
+ local player = players[self.index]
+ self:emit_signal("player::metadata", player)
+ self:emit_signal("player::position", player)
+end
+
+M.inner = playerctl.new {
+ players = {},
+ metadata = {
+ album = "xesam:album",
+ title = "xesam:title",
+ artist = "xesam:artist",
+ art = "mpris:artUrl",
+ },
+}
+
+local instance = gobject { class = M }
+instance:connect_signal("property::index", function(self, index)
+ qpersistent.store("active_player_index", self.index)
+end)
+return instance
diff --git a/.config/awesome/services/playerctl.lua b/.config/awesome/services/playerctl.lua
index f6ee71a..c4be74b 100644
--- a/.config/awesome/services/playerctl.lua
+++ b/.config/awesome/services/playerctl.lua
@@ -1,45 +1,591 @@
-local playerctl = require("lib.bling.signal.playerctl").lib {
- player = { "spotify", "%any" },
+-- CREDIT: https://github.com/kosorin/awesome-rice/blob/b6813bd1bbb3fdd697a1b994784b72daaeaf405d/services/media/playerctl.lua
+
+-- DEPENDENCIES: playerctl
+
+local type = type
+local pairs = pairs
+local ipairs = ipairs
+local gobject = require "gears.object"
+local gtable = require "gears.table"
+local gtimer = require "gears.timer"
+local lgi_playerctl = require("lgi").Playerctl -- /usr/share/gtk-doc/html/playerctl/index.html
+
+---@alias Playerctl.selector
+---|>nil # Select primary player
+---| string # Select player by `instance`
+---| "%all" # Select all players
+
+---@class Playerctl.position_data
+---@field position? integer
+---@field length? integer
+
+---@class Playerctl.data
+---@field name string
+---@field instance string
+---@field playback_status lgi.Playerctl.PlaybackStatus
+---@field position integer
+---@field shuffle boolean
+---@field loop_status lgi.Playerctl.LoopStatus
+---@field volume number
+---@field metadata table<string, any>
+---@field package _position_timer? gears.timer
+
+local playerctl = {
+ lowest_priority = math.maxinteger,
+ any = { name = "%any" },
+ all = { name = "%all" },
}
-playerctl:connect_signal("metadata", function(_, ...)
- awesome.emit_signal("services::playerctl::metadata", ...)
-end)
+---@class Playerctl : gears.object
+---@field unit integer # Number of microseconds in a second.
+---@field package primary_player_data? Playerctl.data
+---@field package player_data table<string, Playerctl.data>
+---@field package tracked_metadata table<string, string>
+---@field package excluded_players table<string, boolean>
+---@field package player_priorities table<string, integer>
+---@field package manager lgi.Playerctl.PlayerManager
+playerctl.object = { unit = 1000000 }
+
+---@param player_data? Playerctl.data
+---@return Playerctl.position_data|nil
+function playerctl.object:get_position_data(player_data)
+ player_data = player_data or self.primary_player_data
+ return (player_data and player_data.metadata)
+ and {
+ position = player_data.position,
+ length = player_data.metadata.length,
+ }
+end
+
+---@param self Playerctl
+---@param instance string
+---@return lgi.Playerctl.Player|nil
+local function find_player_by_instance(self, instance)
+ for _, player in ipairs(self.manager.players) do
+ if player.player_instance == instance then
+ return player
+ end
+ end
+end
+
+---@param self Playerctl
+---@param player_selector Playerctl.selector
+---@param action fun(player: lgi.Playerctl.Player)
+local function for_each_player(self, player_selector, action)
+ local players
+ if not player_selector then
+ local player_data = self.primary_player_data
+ if player_data then
+ players = { find_player_by_instance(self, player_data.instance) }
+ end
+ elseif player_selector == playerctl.all.name then
+ players = self.manager.players
+ elseif type(player_selector) == "string" then
+ players = { find_player_by_instance(self, player_selector) }
+ end
+
+ if players then
+ for _, p in ipairs(players) do
+ action(p)
+ end
+ end
+end
+
+---@param player_selector Playerctl.selector
+function playerctl.object:play_pause(player_selector)
+ for_each_player(self, player_selector, function(p)
+ p:play_pause()
+ end)
+end
+
+---@param player_selector Playerctl.selector
+function playerctl.object:play(player_selector)
+ for_each_player(self, player_selector, function(p)
+ p:play()
+ end)
+end
+
+---@param player_selector Playerctl.selector
+function playerctl.object:pause(player_selector)
+ for_each_player(self, player_selector, function(p)
+ p:pause()
+ end)
+end
+
+---@param player_selector Playerctl.selector
+function playerctl.object:stop(player_selector)
+ for_each_player(self, player_selector, function(p)
+ p:stop()
+ end)
+end
+
+---@param player_selector Playerctl.selector
+function playerctl.object:previous(player_selector)
+ for_each_player(self, player_selector, function(p)
+ p:previous()
+ end)
+end
+
+---@param player_selector Playerctl.selector
+function playerctl.object:next(player_selector)
+ for_each_player(self, player_selector, function(p)
+ p:next()
+ end)
+end
+
+---@param offset integer
+---@param player_selector Playerctl.selector
+function playerctl.object:skip(offset, player_selector)
+ if offset > 0 then
+ self:next(player_selector)
+ else
+ self:previous(player_selector)
+ end
+end
+
+---@param offset integer
+---@param player_selector Playerctl.selector
+function playerctl.object:rewind(offset, player_selector)
+ self:seek(-offset, player_selector)
+end
+
+---@param offset integer
+---@param player_selector Playerctl.selector
+function playerctl.object:fast_forward(offset, player_selector)
+ self:seek(offset, player_selector)
+end
+
+---@param offset integer
+---@param player_selector Playerctl.selector
+function playerctl.object:seek(offset, player_selector)
+ for_each_player(self, player_selector, function(p)
+ p:seek(offset)
+ end)
+end
+
+---@param loop_status lgi.Playerctl.LoopStatus
+---@param player_selector Playerctl.selector
+function playerctl.object:set_loop_status(loop_status, player_selector)
+ loop_status = loop_status:upper()
+ for_each_player(self, player_selector, function(p)
+ p:set_loop_status(loop_status)
+ end)
+end
+
+---@param player_selector Playerctl.selector
+function playerctl.object:cycle_loop_status(player_selector)
+ for_each_player(self, player_selector, function(p)
+ if p.loop_status == "NONE" then
+ p:set_loop_status "TRACK"
+ elseif p.loop_status == "TRACK" then
+ p:set_loop_status "PLAYLIST"
+ elseif p.loop_status == "PLAYLIST" then
+ p:set_loop_status "NONE"
+ end
+ end)
+end
+
+---@param position integer
+---@param player_selector Playerctl.selector
+function playerctl.object:set_position(position, player_selector)
+ for_each_player(self, player_selector, function(p)
+ p:set_position(position)
+ end)
+end
+
+---@param shuffle boolean
+---@param player_selector Playerctl.selector
+function playerctl.object:set_shuffle(shuffle, player_selector)
+ for_each_player(self, player_selector, function(p)
+ p:set_shuffle(shuffle)
+ end)
+end
+
+---@param player_selector Playerctl.selector
+function playerctl.object:toggle_shuffle(player_selector)
+ for_each_player(self, player_selector, function(p)
+ p:set_shuffle(not p.shuffle)
+ end)
+end
+
+---@param volume number
+---@param player_selector Playerctl.selector
+function playerctl.object:set_volume(volume, player_selector)
+ for_each_player(self, player_selector, function(p)
+ p:set_volume(volume)
+ end)
+end
+
+---@param player_data? Playerctl.data
+---@return boolean
+function playerctl.object:is_primary_player(player_data)
+ return self.primary_player_data == player_data
+end
+
+---@return Playerctl.data|nil
+function playerctl.object:get_primary_player_data()
+ return self.primary_player_data
+end
+
+---@param self Playerctl
+---@param player? lgi.Playerctl.Player
+local function update_primary_player(self, player)
+ if player then
+ self.manager:move_player_to_top(player)
+ end
+
+ local primary_player = self.manager.players[1]
+
+ local old = self.primary_player_data
+ local new = self.player_data[primary_player and primary_player.player_instance]
+ if old ~= new then
+ self.primary_player_data = new
+ self:emit_signal("player::primary", new, old)
+ end
+end
+
+---@param player_data Playerctl.data
+---@return boolean
+function playerctl.object:is_pinned(player_data)
+ local player_instance = player_data and player_data.instance or nil
+ return self.pinned_player_instance == player_instance
+end
+
+---@param player_data? Playerctl.data
+function playerctl.object:pin(player_data)
+ local player_instance = player_data and player_data.instance or nil
+ if self.pinned_player_instance ~= player_instance then
+ self.pinned_player_instance = player_instance
+ self:emit_signal("player::pinned", self.pinned_player_instance)
+
+ local player
+ if player_data then
+ player = find_player_by_instance(self, player_data.instance)
+ elseif self.primary_player_data then
+ player = find_player_by_instance(self, self.primary_player_data.instance)
+ end
+
+ update_primary_player(self, player)
+ end
+end
+
+---@return Playerctl.data[]
+function playerctl.object:list()
+ ---@type Playerctl.data[]
+ local players = {}
+ for _, p in ipairs(self.manager.players) do
+ players[#players + 1] = self.player_data[p.player_instance]
+ end
+ return players
+end
+
+---@param player_data Playerctl.data
+local function refresh_position_timer(player_data)
+ if player_data.playback_status == "PLAYING" then
+ player_data._position_timer:again()
+ else
+ player_data._position_timer:stop()
+ end
+end
+
+---@param self Playerctl
+---@param player_data Playerctl.data
+---@param by_timer? boolean
+local function update_position(self, player_data, by_timer)
+ local player = find_player_by_instance(self, player_data.instance)
+ if player then
+ player_data.position = player:get_position()
+ self:emit_signal("player::position", player_data, by_timer)
+ end
+end
-playerctl:connect_signal("position", function(_, ...)
- awesome.emit_signal("services::playerctl::position", ...)
-end)
+---@param self Playerctl
+---@param player_data Playerctl.data
+---@param metadata lgi.Playerctl.Metadata
+---@return boolean
+---@return table<string, boolean>
+local function update_metadata(self, player_data, metadata)
+ metadata = metadata and metadata.value or {}
-playerctl:connect_signal("playback_status", function(_, ...)
- awesome.emit_signal("services::playerctl::playback_status", ...)
-end)
+ local changed = false
+ local changed_data = {}
-playerctl:connect_signal("seeked", function(_, ...)
- awesome.emit_signal("services::playerctl::seeked", ...)
-end)
+ local function mark_changed(name)
+ changed = true
+ changed_data[name] = true
+ end
-playerctl:connect_signal("volume", function(_, ...)
- awesome.emit_signal("services::playerctl::volume", ...)
-end)
+ local target_metadata = player_data.metadata
+ for name, mpris_name in pairs(self.tracked_metadata) do
+ local value = metadata[mpris_name]
+ local value_type = type(value)
+ if value_type == "nil" or value_type == "boolean" or value_type == "number" or value_type == "string" then
+ if target_metadata[name] ~= value then
+ target_metadata[name] = value
+ mark_changed(name)
+ end
+ elseif value_type == "userdata" and value.type == "as" then
+ local old = target_metadata[name]
+ if type(old) ~= "table" then
+ old = {}
+ end
-playerctl:connect_signal("loop_status", function(_, ...)
- awesome.emit_signal("services::playerctl::loop_status", ...)
-end)
+ local new = {}
+ for _, s in value:ipairs() do
+ new[#new + 1] = s
+ end
-playerctl:connect_signal("shuffle", function(_, ...)
- awesome.emit_signal("services::playerctl::shuffle", ...)
-end)
+ target_metadata[name] = new
-playerctl:connect_signal("exit", function(_, ...)
- awesome.emit_signal("services::playerctl::exit", ...)
-end)
+ if #old ~= #new then
+ mark_changed(name)
+ else
+ for i = 1, #new do
+ if old[i] ~= new[i] then
+ mark_changed(name)
+ break
+ end
+ end
+ end
+ else
+ if target_metadata[name] ~= nil then
+ target_metadata[name] = nil
+ mark_changed(name)
+ end
+ end
+ end
-playerctl:connect_signal("exit", function(_, ...)
- awesome.emit_signal("services::playerctl::exit", ...)
-end)
+ return changed, changed_data
+end
-playerctl:connect_signal("no_players", function()
- awesome.emit_signal "services::playerctl::no_players"
-end)
+---@param self Playerctl
+---@param full_name lgi.Playerctl.PlayerName
+---@return lgi.Playerctl.Player
+local function manage_player(self, full_name)
+ local new_player = lgi_playerctl.Player.new_from_name(full_name)
-return playerctl
+ function new_player.on_metadata(p, metadata)
+ local player_data = self.player_data[p.player_instance]
+ if player_data and update_metadata(self, player_data, metadata) then
+ self:emit_signal("player::metadata", player_data)
+
+ update_position(self, player_data)
+ refresh_position_timer(player_data)
+ end
+ end
+
+ function new_player.on_playback_status(p, playback_status)
+ update_primary_player(self, p)
+
+ local player_data = self.player_data[p.player_instance]
+ if player_data and player_data.playback_status ~= playback_status then
+ player_data.playback_status = playback_status
+ self:emit_signal("player::playback_status", player_data)
+
+ update_position(self, player_data)
+ refresh_position_timer(player_data)
+ end
+ end
+
+ function new_player.on_seeked(p, position)
+ local player_data = self.player_data[p.player_instance]
+ if player_data and player_data.position ~= position then
+ player_data.position = position
+ self:emit_signal("player::position", player_data)
+
+ refresh_position_timer(player_data)
+ end
+ end
+
+ function new_player.on_shuffle(p, shuffle)
+ local player_data = self.player_data[p.player_instance]
+ if player_data and player_data.shuffle ~= shuffle then
+ player_data.shuffle = shuffle
+ self:emit_signal("player::shuffle", player_data)
+ end
+ end
+
+ function new_player.on_loop_status(p, loop_status)
+ local player_data = self.player_data[p.player_instance]
+ if player_data and player_data.loop_status ~= loop_status then
+ player_data.loop_status = loop_status
+ self:emit_signal("player::loop_status", player_data)
+ end
+ end
+
+ function new_player.on_volume(p, volume)
+ local player_data = self.player_data[p.player_instance]
+ if player_data and player_data.volume ~= volume then
+ player_data.volume = volume
+ self:emit_signal("player::volume", player_data)
+ end
+ end
+
+ self.manager:manage_player(new_player)
+
+ return new_player
+end
+
+---@param self Playerctl
+---@param player_name string
+---@return boolean
+local function filter_name(self, player_name)
+ if self.excluded_players[player_name] then
+ return false
+ end
+ if self.player_priorities[playerctl.any] or self.player_priorities[player_name] then
+ return true
+ end
+ return false
+end
+
+---@param self Playerctl
+---@param player_a lgi.Playerctl.Player
+---@param player_b lgi.Playerctl.Player
+---@return sign
+local function compare_players(self, player_a, player_b)
+ if player_a.player_name < player_b.player_name then
+ return -1
+ elseif player_a.player_name > player_b.player_name then
+ return 1
+ else
+ return 0
+ end
+end
+
+---@param self Playerctl
+local function initialize_manager(self)
+ self.player_data = {}
+
+ self.manager = lgi_playerctl.PlayerManager()
+ self.manager:set_sort_func(function(a, b)
+ local player_a = lgi_playerctl.Player(a)
+ local player_b = lgi_playerctl.Player(b)
+ return compare_players(self, player_a, player_b)
+ end)
+
+ ---@param full_name lgi.Playerctl.PlayerName
+ ---@return lgi.Playerctl.Player|nil
+ local function try_manage(full_name)
+ if filter_name(self, full_name.name) then
+ return manage_player(self, full_name)
+ end
+ end
+
+ function self.manager.on_name_appeared(_, full_name)
+ try_manage(full_name)
+ end
+
+ function self.manager.on_player_appeared(_, player)
+ ---@type Playerctl.data
+ local player_data = {
+ name = player.player_name,
+ instance = player.player_instance,
+ playback_status = player.playback_status,
+ position = player.position,
+ shuffle = player.shuffle,
+ loop_status = player.loop_status,
+ volume = player.volume,
+ metadata = {},
+ }
+ update_metadata(self, player_data, player.metadata)
+
+ player_data._position_timer = gtimer {
+ timeout = 1,
+ callback = function()
+ update_position(self, player_data, true)
+ end,
+ }
+ refresh_position_timer(player_data)
+
+ self.player_data[player_data.instance] = player_data
+ self:emit_signal("player::appeared", player_data)
+
+ update_primary_player(self, player)
+ end
+
+ function self.manager.on_player_vanished(_, player)
+ update_primary_player(self)
+
+ if self.pinned_player_instance == player.player_instance then
+ self:pin(nil)
+ end
+
+ local player_data = self.player_data[player.player_instance]
+ if player_data then
+ player_data._position_timer:stop()
+ self:emit_signal("player::vanished", player_data)
+ self.player_data[player.player_instance] = nil
+ end
+ end
+
+ for _, full_name in ipairs(self.manager.player_names) do
+ try_manage(full_name)
+ end
+
+ update_primary_player(self)
+end
+
+---@param self Playerctl
+---@param args? Playerctl.new.args
+local function parse_args(self, args)
+ args = args or {}
+
+ self.tracked_metadata = args.metadata or {}
+
+ -- Always track length
+ self.tracked_metadata.length = "mpris:length"
+
+ local excluded_players = {}
+ if type(args.excluded_players) == "string" then
+ excluded_players[args.excluded_players] = true
+ elseif args.excluded_players then
+ for _, name in ipairs(args.excluded_players) do
+ excluded_players[name] = true
+ end
+ end
+ self.excluded_players = excluded_players
+
+ local function get_priority_key(name)
+ return name == playerctl.any.name and playerctl.any or name
+ end
+ local player_priorities
+ if type(args.players) == "string" then
+ player_priorities = { [get_priority_key(args.players)] = 1 }
+ elseif type(args.players) == "table" and #args.players > 0 then
+ player_priorities = {}
+ for i, name in ipairs(args.players) do
+ player_priorities[get_priority_key(name)] = i
+ end
+ else
+ player_priorities = { [playerctl.any] = 1 }
+ end
+ self.player_priorities = player_priorities
+end
+
+---@class Playerctl.new.args
+---@field players string[]
+---@field excluded_players? string[]
+---@field metadata table<string, string>
+
+---@param args? Playerctl.new.args
+---@return Playerctl
+function playerctl.new(args)
+ local self = gtable.crush(gobject {}, playerctl.object, true) --[[@as Playerctl]]
+
+ parse_args(self, args)
+
+ initialize_manager(self)
+
+ return self
+end
+
+return playerctl.new {
+ players = {},
+ metadata = {
+ album = "xesam:album",
+ title = "xesam:title",
+ artist = "xesam:artist",
+ art = "mpris:artUrl",
+ },
+}
diff --git a/.config/awesome/signals/client.lua b/.config/awesome/signals/client.lua
index 8788735..f0b5136 100644
--- a/.config/awesome/signals/client.lua
+++ b/.config/awesome/signals/client.lua
@@ -1,9 +1,9 @@
-local qvars = require "quarrel.vars"
+local qui = require "quarrel.ui"
local rectangle = require("gears.shape").rectangle
-local conductor = require "ui.conductor"
+-- local conductor = require "ui.conductor"
client.connect_signal("request::manage", function(c)
- c.shape = qvars.shape
+ c.shape = qui.shape
if c.maximized then
c.maximized = false
c.maximized = true
@@ -17,8 +17,8 @@ local function handle_corners(c)
c.shape = rectangle
c.border_width = 0
else
- c.shape = qvars.shape
- c.border_width = qvars.border_width
+ c.shape = qui.shape
+ c.border_width = qui.BORDER_WIDTH
end
end
diff --git a/.config/awesome/ui/conductor/init.lua b/.config/awesome/ui/conductor/init.lua
index e6fe552..402945c 100644
--- a/.config/awesome/ui/conductor/init.lua
+++ b/.config/awesome/ui/conductor/init.lua
@@ -1,8 +1,64 @@
local awful = require "awful"
+local gshape = require "gears.shape"
+local phosphor = require "assets.phosphor"
local q = require "quarrel"
local qui = require "quarrel.ui"
local qvars = require "quarrel.vars"
+local wibox = require "wibox"
-local conductor = {}
+local M = {}
-return conductor
+M._popup = qui.popup {
+ -- visible = false,
+ ontop = true,
+ placement = "right",
+ shape = function(cr, w, h)
+ gshape.partially_rounded_rect(cr, w, h, true, false, false, true, qui.BORDER_RADIUS)
+ end,
+ -- x = awful.screen.focused().geometry.width,
+ -- minimum_width = width,
+ -- maximum_width = width,
+ -- maximum_height = max_height,
+ widget = awful.widget.tasklist {
+ screen = awful.screen.focused(),
+ filter = awful.widget.tasklist.filter.allscreen,
+ layout = {
+ spacing = qui.BIG_PADDING,
+ layout = wibox.layout.fixed.vertical,
+ },
+ widget_template = qui.styled {
+ widget = wibox.container.background,
+ {
+ {
+ widget = wibox.container.constraint,
+ strategy = "max",
+ height = qui.CHAR_HEIGHT,
+ width = qui.CHAR_HEIGHT,
+ {
+ widget = wibox.widget.imagebox,
+ id = "icon_role",
+ },
+ },
+ {
+ widget = wibox.container.constraint,
+ strategy = "max",
+ width = qui.CHAR_WIDTH * 24,
+ {
+ widget = wibox.widget.textbox,
+ id = "client_name",
+ },
+ },
+ layout = wibox.layout.fixed.horizontal,
+ spacing = qui.PADDING,
+ },
+ create_callback = function(self, c)
+ self:get_children_by_id("client_name")[1].text = c.icon_name
+ end,
+ update_callback = function(self, c)
+ self:get_children_by_id("client_name")[1].text = c.name
+ end,
+ },
+ },
+}
+
+return M
diff --git a/.config/awesome/ui/decorations/wallpaper.lua b/.config/awesome/ui/decorations/wallpaper.lua
index 3a204c2..cb01745 100644
--- a/.config/awesome/ui/decorations/wallpaper.lua
+++ b/.config/awesome/ui/decorations/wallpaper.lua
@@ -1,12 +1,12 @@
+local qcolor = require "quarrel.color"
local qmarkup = require "quarrel.markup"
local qui = require "quarrel.ui"
-local qvars = require "quarrel.vars"
local wallpaper = require "awful.wallpaper"
local wibox = require "wibox"
screen.connect_signal("request::wallpaper", function(s)
wallpaper {
- bg = qvars.colors.dim.bg,
+ bg = qcolor.palette.bg.low,
screen = s,
widget = {
widget = wibox.container.place,
@@ -16,12 +16,12 @@ screen.connect_signal("request::wallpaper", function(s)
widget = wibox.widget.textbox,
font = qui.font(1.5),
markup = table.concat({
- qmarkup(" ___", { fg = qvars.colors.red }),
- qmarkup(" /\\ \\", { fg = qvars.colors.green }),
- qmarkup(" /::\\ \\", { fg = qvars.colors.yellow }),
- qmarkup("/::\\:\\__\\", { fg = qvars.colors.blue }),
- qmarkup("\\/\\::/ /", { fg = qvars.colors.pink }),
- qmarkup(" \\/__/", { fg = qvars.colors.cyan }),
+ qmarkup(" ___", { fg = qcolor.palette.blue() }),
+ qmarkup(" /\\ \\", { fg = qcolor.palette.green() }),
+ qmarkup(" /::\\ \\", { fg = qcolor.palette.yellow() }),
+ qmarkup("/::\\:\\__\\", { fg = qcolor.palette.orange() }),
+ qmarkup("\\/\\::/ /", { fg = qcolor.palette.red() }),
+ qmarkup(" \\/__/", { fg = qcolor.palette.pink() }),
}, "\n"),
},
},
diff --git a/.config/awesome/ui/fresnel/init.lua b/.config/awesome/ui/fresnel/init.lua
index 9905741..5d091b6 100644
--- a/.config/awesome/ui/fresnel/init.lua
+++ b/.config/awesome/ui/fresnel/init.lua
@@ -1,8 +1,10 @@
local al_prompt = require "lib.bling.widget.app_launcher.prompt"
local awful = require "awful"
-local beautiful = require "beautiful"
local cfg = require "misc.cfg"
+local gshape = require "gears.shape"
local gtable = require "gears.table"
+local qanim = require "quarrel.animation"
+local qcolor = require "quarrel.color"
local qnative = require "quarrel.native"
local qtable = require "quarrel.table"
local qui = require "quarrel.ui"
@@ -42,8 +44,8 @@ function fresnel:_update(query, scrolled)
self._entries_offset = 0
end
- local layout = self._widget.widget:get_children_by_id("entry_layout")[1]
- local status = self._widget.widget:get_children_by_id("status")[1]
+ local layout = self._popup.widget:get_children_by_id("entry_layout")[1]
+ local status = self._popup.widget:get_children_by_id("status")[1]
local all_providers = {}
local entries_count = 0
@@ -71,18 +73,18 @@ function fresnel:_update(query, scrolled)
local entry_widget = wibox.widget {
widget = wibox.container.background,
- shape = qvars.shape,
+ shape = qui.shape,
{
widget = wibox.container.margin,
- margins = qvars.padding,
+ margins = qui.PADDING,
{
widget = wibox.container.constraint,
strategy = "max",
- height = qvars.char_height,
+ height = qui.CHAR_HEIGHT,
{
{
widget = wibox.container.background,
- fg = qvars.colors.dim.fg,
+ fg = qcolor.palette.low,
{
widget = wibox.widget.textbox,
text = entry.provider .. " | ",
@@ -92,7 +94,7 @@ function fresnel:_update(query, scrolled)
widget = wibox.widget.textbox,
text = entry.message,
},
- spacing = qvars.padding,
+ spacing = qui.PADDING,
layout = wibox.layout.fixed.horizontal,
},
},
@@ -111,21 +113,21 @@ function fresnel:_update(query, scrolled)
if self._selected_index + self._entries_offset == i then
entry_widget._selected = true
- entry_widget.bg = qvars.colors.black
+ entry_widget.bg = qcolor.palette.bg.high
end
entry_widget:connect_signal("mouse::enter", function()
if entry_widget._selected == true then
return
end
- entry_widget.bg = qvars.colors.black
+ entry_widget.bg = qcolor.palette.bg.high
end)
entry_widget:connect_signal("mouse::leave", function()
if entry_widget._selected == true then
return
end
- entry_widget.bg = qvars.colors.bg
+ entry_widget.bg = qcolor.palette.bg()
end)
layout:add(entry_widget)
@@ -143,7 +145,7 @@ fresnel._prompt = al_prompt {
prompt = "",
reset_on_stop = true,
ul_cursor = "low",
- bg_cursor = qvars.colors.black,
+ bg_cursor = qcolor.palette.bg.high,
changed_callback = function(text)
if fresnel._text == text then
return
@@ -192,27 +194,31 @@ fresnel._prompt = al_prompt {
end,
}
-fresnel._widget = qui.popup {
- visible = false,
+local max_height = qui.BIG_PADDING * 2 + (qui.BIG_PADDING * 2 + qui.CHAR_HEIGHT) * 10
+-- + qui.PADDING
+-- + qui.PADDING * 9
+
+local width = awful.screen.focused().geometry.width / 2
+
+fresnel._popup = qui.popup {
+ -- visible = false,
ontop = true,
- placement = function(d)
- awful.placement.top(d, {
- margins = {
- top = beautiful.useless_gap * 2,
- },
- })
+ placement = false,
+ shape = function(cr, w)
+ gshape.partially_rounded_rect(cr, w, 0, false, false, true, true, qui.BORDER_RADIUS)
end,
- minimum_width = awful.screen.focused().geometry.width / 2,
- maximum_width = awful.screen.focused().geometry.width / 2,
+ x = width / 2,
+ minimum_width = width,
+ maximum_width = width,
+ -- maximum_height = max_height,
widget = {
- {
+ qui.styled {
widget = wibox.container.background,
- bg = qvars.colors.black,
- fg = qvars.colors.dim.fg,
- shape = qvars.shape,
+ fg = qcolor.palette.fg.low,
+ bg = qcolor.palette.bg.high,
{
widget = wibox.container.margin,
- margins = qvars.padding,
+ margins = qui.BIG_PADDING,
{
{
widget = wibox.widget.textbox,
@@ -221,16 +227,16 @@ fresnel._widget = qui.popup {
{
widget = wibox.container.margin,
margins = {
- left = qvars.padding,
- right = qvars.padding,
+ left = qui.PADDING,
+ right = qui.PADDING,
},
{
widget = wibox.container.constraint,
strategy = "max",
- height = qvars.char_height,
+ height = qui.CHAR_HEIGHT,
{
widget = wibox.container.background,
- fg = qvars.colors.fg,
+ fg = qcolor.palette.fg(),
fresnel._prompt.textbox,
},
},
@@ -247,10 +253,10 @@ fresnel._widget = qui.popup {
{
widget = wibox.container.margin,
margins = {
- top = qvars.padding,
+ top = qui.PADDING,
},
{
- spacing = qvars.padding,
+ spacing = qui.PADDING,
layout = wibox.layout.fixed.vertical,
id = "entry_layout",
},
@@ -258,35 +264,43 @@ fresnel._widget = qui.popup {
layout = wibox.layout.align.vertical,
},
}
-fresnel._widget.maximum_height = qvars.big_padding * 2
- + (qvars.padding * 2 + qvars.char_height) * (1 + 10)
- + qvars.padding
- + qvars.padding * 9
-
function fresnel:show()
self._toggled = true
- self._timed.target = 1
+ self._opacity_timed.target = 1
+ self._height_timed:set(max_height)
self:_update()
self._prompt:start()
end
function fresnel:hide()
self._toggled = false
- self._timed.target = 0
+ self._opacity_timed.target = 0
+ self._height_timed:set(0)
self._prompt:stop()
end
-fresnel._timed = rubato.timed {
+fresnel._height_timed = qanim:new {
+ duration = qvars.anim_duration,
+ pos = 0,
+ easing = qvars.easing,
+ subscribed = function(pos)
+ fresnel._popup.shape = function(cr, w)
+ gshape.partially_rounded_rect(cr, w, pos, false, false, true, true, qui.BORDER_RADIUS)
+ end
+ end,
+}
+
+-- TODO: optimize the search algo to be more efficient and not require making fresnel invisible
+fresnel._opacity_timed = rubato.timed {
duration = qvars.anim_duration,
- intro = qvars.anim_intro,
pos = 0,
subscribed = function(pos)
- fresnel._widget.opacity = pos
+ fresnel._popup.opacity = pos
if pos == 0 then
- fresnel._widget.visible = false
+ fresnel._popup.visible = false
else
- fresnel._widget.visible = true
+ fresnel._popup.visible = true
end
end,
}
diff --git a/.config/awesome/ui/init.lua b/.config/awesome/ui/init.lua
index dd93ad1..6ad1c7b 100644
--- a/.config/awesome/ui/init.lua
+++ b/.config/awesome/ui/init.lua
@@ -1,4 +1,4 @@
require "ui.statusbar"
require "ui.decorations"
-require "ui.conductor"
-require "ui.osd"
+-- require "ui.conductor"
+-- require "ui.osd"
diff --git a/.config/awesome/ui/insightful/init.lua b/.config/awesome/ui/insightful/init.lua
index 3dcbbea..be072d5 100644
--- a/.config/awesome/ui/insightful/init.lua
+++ b/.config/awesome/ui/insightful/init.lua
@@ -1,6 +1,6 @@
local awful = require "awful"
local beautiful = require "beautiful"
-local qstore = require "quarrel.store"
+local qbind = require "quarrel.bind"
local qtable = require "quarrel.table"
local qui = require "quarrel.ui"
local qvars = require "quarrel.vars"
@@ -81,7 +81,7 @@ insightful._widget = qui.popup {
minimum_width = awful.screen.focused().geometry.width / 2,
widget = {
layout = wibox.layout.fixed.vertical,
- spacing = qvars.big_padding,
+ spacing = qui.BIG_PADDING,
id = "layout_container",
},
}
@@ -90,7 +90,7 @@ function insightful:_generate()
local grouped_binds = {}
local layout_container = insightful._widget.widget.layout_container
- for _, keybind in ipairs(qstore.bindings) do
+ for _, keybind in ipairs(qbind.bindings) do
local group = keybind.group or "general"
local group_exists = grouped_binds[group] ~= nil
@@ -107,7 +107,7 @@ function insightful:_generate()
for group, keybinds in qtable.opairs(grouped_binds) do
local group_layout = {
- spacing = qvars.padding,
+ spacing = qui.PADDING,
layout = wibox.layout.fixed.vertical,
}
@@ -129,11 +129,11 @@ function insightful:_generate()
1,
qui.styled {
widget = wibox.container.background,
- bg = qvars.colors.bright.black,
+ bg = qcolor.palette.bright.black,
border_width = 0,
{
widget = wibox.container.margin,
- margins = qvars.padding,
+ margins = qui.PADDING,
{
widget = wibox.widget.textbox,
text = insightful._keymap[mod] or mod,
@@ -166,7 +166,7 @@ function insightful:_generate()
if keybind.desc then
key_and_desc_layout[3] = {
widget = wibox.container.background,
- fg = qvars.colors.dim.fg,
+ fg = qcolor.palette.dim.fg,
{
widget = wibox.widget.textbox,
text = keybind.desc,
@@ -183,12 +183,12 @@ function insightful:_generate()
{
{
widget = wibox.container.background,
- bg = qvars.colors.yellow,
- fg = qvars.colors.bg,
- shape = qvars.shape,
+ bg = qcolor.palette.yellow,
+ fg = qcolor.palette.bg,
+ shape = qui.shape,
{
widget = wibox.container.margin,
- margins = qvars.padding,
+ margins = qui.PADDING,
{
widget = wibox.widget.textbox,
text = group,
@@ -199,7 +199,7 @@ function insightful:_generate()
layout = wibox.layout.align.horizontal,
},
group_layout,
- spacing = qvars.padding,
+ spacing = qui.PADDING,
layout = wibox.layout.fixed.vertical,
}
end
diff --git a/.config/awesome/ui/osd.lua b/.config/awesome/ui/osd.lua
deleted file mode 100644
index fc74ed9..0000000
--- a/.config/awesome/ui/osd.lua
+++ /dev/null
@@ -1,102 +0,0 @@
---- Heavy inspiration from a design made by https://github.com/tsukki9696
-
-local awful = require "awful"
-local beautiful = require "beautiful"
-local gtimer = require "gears.timer"
-local phosphor = require "assets.phosphor"
-local qmath = require "quarrel.math"
-local qui = require "quarrel.ui"
-local qvars = require "quarrel.vars"
-local rubato = require "lib.rubato"
-local wibox = require "wibox"
-
-local osd = {}
-
-local widget = qui.popup {
- ontop = true,
- visible = false,
- placement = function(d)
- awful.placement.top(d, {
- margins = {
- top = beautiful.useless_gap * 2,
- },
- })
- end,
- minimum_height = qvars.char_height * 2,
- -- minimum_width = awful.screen.focused().geometry.width / 2,
- widget = {
- {
- widget = wibox.container.place,
-
- qui.icon {
- icon = phosphor.speaker_simple_none_fill,
- widget = {
- forced_height = qvars.char_height * 1.2,
- forced_width = qvars.char_height * 1.2,
- id = "icon",
- },
- },
- },
- {
- widget = wibox.container.place,
- {
- widget = wibox.widget.progressbar,
- max_value = 100,
- value = 20,
- forced_height = qvars.char_height / 1.5,
- forced_width = qvars.expanded_bar_size
- - (qvars.big_padding + qvars.big_padding * 2 + qvars.padding * 2)
- - (qvars.char_height / 1.25 + qvars.padding) * 3,
- color = qvars.colors.yellow,
- background_color = qvars.colors.black,
- shape = qvars.shape,
- id = "progress",
- },
- },
-
- {
- widget = wibox.widget.textbox,
- text = "20%",
- font = qui.font(1.2),
- id = "percentage",
- },
- layout = wibox.layout.fixed.horizontal,
- spacing = qvars.big_padding,
- },
-}
-
-local timer
-
-local anim = rubato.timed {
- duration = qvars.anim_duration,
- intro = qvars.anim_intro,
- pos = 0,
- subscribed = function(pos)
- widget.opacity = pos
- if pos == 0 then
- widget.visible = false
- elseif not widget.visible then
- widget.visible = true
- elseif pos == 1 then
- timer:start()
- end
- end,
-}
-
-timer = gtimer {
- timeout = 1,
- callback = function()
- anim.target = 0
- end,
- single_shot = true,
-}
-
-function osd.notify(icon, value, max)
- anim.target = 1
- widget:get_children_by_id("icon")[1].image = icon
- widget:get_children_by_id("progress")[1].value = value
- widget:get_children_by_id("progress")[1].max_value = max
- widget:get_children_by_id("percentage")[1].text = tostring(qmath.percentage(value, max)) .. "%"
-end
-
-return osd
diff --git a/.config/awesome/ui/osd/init.lua b/.config/awesome/ui/osd/init.lua
new file mode 100644
index 0000000..4310f17
--- /dev/null
+++ b/.config/awesome/ui/osd/init.lua
@@ -0,0 +1,102 @@
+local awful = require "awful"
+local beautiful = require "beautiful"
+local gtimer = require "gears.timer"
+local phosphor = require "assets.phosphor"
+local qmath = require "quarrel.math"
+local qui = require "quarrel.ui"
+local qvars = require "quarrel.vars"
+local rubato = require "lib.rubato"
+local wibox = require "wibox"
+-- local mpris_widget = require "ui.osd.mpris"
+
+local osd = {}
+
+local widget = awful.popup(qui.styled {
+ ontop = true,
+ visible = true,
+ placement = function(d)
+ awful.placement.top(d, {
+ margins = {
+ top = beautiful.useless_gap * 2,
+ },
+ })
+ end,
+ minimum_height = qui.CHAR_HEIGHT * 3,
+ -- widget = mpris_widget
+ -- minimum_width = awful.screen.focused().geometry.width / 2,
+ -- widget = {
+ -- {
+ -- widget = wibox.container.place,
+ --
+ -- qui.icon {
+ -- icon = phosphor.speaker_simple_none_fill,
+ -- widget = {
+ -- forced_height = qui.CHAR_HEIGHT * 1.2,
+ -- forced_width = qui.CHAR_HEIGHT * 1.2,
+ -- id = "icon",
+ -- },
+ -- },
+ -- },
+ -- {
+ -- widget = wibox.container.place,
+ -- {
+ -- widget = wibox.widget.progressbar,
+ -- max_value = 100,
+ -- value = 20,
+ -- forced_height = qui.CHAR_HEIGHT / 1.5,
+ -- forced_width = qvars.expanded_bar_size
+ -- - (qui.BIG_PADDING + qui.BIG_PADDING * 2 + qui.PADDING * 2)
+ -- - (qui.CHAR_HEIGHT / 1.25 + qui.PADDING) * 3,
+ -- color = qcolor.palette.yellow(),
+ -- background_color = qcolor.palette.border.variant,
+ -- shape = qui.shape,
+ -- id = "progress",
+ -- },
+ -- },
+ --
+ -- {
+ -- widget = wibox.widget.textbox,
+ -- text = "20%",
+ -- font = qui.font(1.2),
+ -- id = "percentage",
+ -- },
+ -- layout = wibox.layout.fixed.horizontal,
+ -- spacing = qui.BIG_PADDING,
+ -- },
+})
+
+local timer
+
+local anim = rubato.timed {
+ duration = qvars.anim_duration,
+ intro = qvars.anim_intro,
+ pos = 1,
+ subscribed = function(pos)
+ widget.opacity = pos
+ if pos == 0 then
+ widget.visible = false
+ elseif not widget.visible then
+ widget.visible = true
+ elseif pos == 1 then
+ -- timer:start()
+ end
+ end,
+}
+
+timer = gtimer {
+ timeout = 1,
+ callback = function()
+ -- anim.target = 0
+ end,
+ single_shot = true,
+}
+
+function osd.notify(icon, value, max)
+ anim.target = 1
+ widget:get_children_by_id("icon")[1].image = icon
+ widget:get_children_by_id("progress")[1].value = value
+ widget:get_children_by_id("progress")[1].max_value = max
+ widget:get_children_by_id("percentage")[1].text = tostring(qmath.percentage(value, max)) .. "%"
+end
+
+return osd
diff --git a/.config/awesome/ui/powermenu/init.lua b/.config/awesome/ui/powermenu/init.lua
index 81d2ea3..2e2b4af 100644
--- a/.config/awesome/ui/powermenu/init.lua
+++ b/.config/awesome/ui/powermenu/init.lua
@@ -1,5 +1,6 @@
local awful = require "awful"
local phosphor = require "assets.phosphor"
+local qcolor = require "quarrel.color"
local qui = require "quarrel.ui"
local qvars = require "quarrel.vars"
local rubato = require "lib.rubato"
@@ -8,21 +9,21 @@ local wibox = require "wibox"
local function create_button(title, icon, color, exec)
return wibox.widget {
widget = wibox.container.background,
- shape = qvars.shape,
+ shape = qui.shape,
{
widget = wibox.container.margin,
- margins = qvars.big_padding,
+ margins = qui.BIG_PADDING,
{
qui.styled {
widget = wibox.container.background,
border_color = color,
{
widget = wibox.container.margin,
- margins = qvars.big_padding,
+ margins = qui.BIG_PADDING,
qui.icon {
widget = {
- forced_height = qvars.char_height * 4 - qvars.big_padding * 2,
- forced_width = qvars.char_height * 4 - qvars.big_padding * 2,
+ forced_height = qui.CHAR_HEIGHT * 4 - qui.BIG_PADDING * 2,
+ forced_width = qui.CHAR_HEIGHT * 4 - qui.BIG_PADDING * 2,
},
icon = icon,
color = color,
@@ -37,12 +38,12 @@ local function create_button(title, icon, color, exec)
},
},
layout = wibox.layout.fixed.vertical,
- spacing = qvars.big_padding,
+ spacing = qui.BIG_PADDING,
exec = exec,
},
},
select = function(self)
- self.bg = qvars.colors.black
+ self.bg = qcolor.palette.bg.high
end,
}
end
@@ -56,7 +57,7 @@ screen.connect_signal("request::desktop_decoration", function(s)
visible = false,
minimum_width = s.geometry.width,
minimum_height = s.geometry.height,
- bg = qvars.colors.bg .. "ee",
+ bg = qcolor.palette.bg() .. "ee",
border_width = 0,
widget = {
widget = wibox.container.place,
@@ -64,10 +65,10 @@ screen.connect_signal("request::desktop_decoration", function(s)
widget = wibox.container.background,
{
widget = wibox.container.margin,
- margins = qvars.big_padding,
+ margins = qui.BIG_PADDING,
{
layout = wibox.layout.fixed.horizontal,
- spacing = qvars.big_padding * 2,
+ spacing = qui.BIG_PADDING * 2,
id = "list",
},
},
@@ -77,16 +78,16 @@ screen.connect_signal("request::desktop_decoration", function(s)
local layout = powermenu._widget.widget:get_children_by_id("list")[1]
- layout:add(create_button("Shutdown", phosphor.power_bold, qvars.colors.red, function()
+ layout:add(create_button("Shutdown", phosphor.power_bold, qcolor.palette.red(), function()
awful.spawn "poweroff"
end))
- layout:add(create_button("Log out", phosphor.sign_out_bold, qvars.colors.green, function()
+ layout:add(create_button("Log out", phosphor.sign_out_bold, qcolor.palette.green(), function()
awesome.quit()
end))
- layout:add(create_button("Lock", phosphor.lock_simple_fill, qvars.colors.blue, function()
+ layout:add(create_button("Lock", phosphor.lock_simple_fill, qcolor.palette.blue(), function()
require("quarrel").debug "locked!l"
end))
- layout:add(create_button("Restart", phosphor.arrow_clockwise_bold, qvars.colors.pink, function()
+ layout:add(create_button("Restart", phosphor.arrow_clockwise_bold, qcolor.palette.pink(), function()
awful.spawn "reboot"
end))
end)
diff --git a/.config/awesome/ui/statusbar/consts.lua b/.config/awesome/ui/statusbar/consts.lua
new file mode 100644
index 0000000..00d01bf
--- /dev/null
+++ b/.config/awesome/ui/statusbar/consts.lua
@@ -0,0 +1,8 @@
+local qui = require "quarrel.ui"
+local C = {}
+
+C.BAR_SIZE = 24 + qui.BIG_PADDING * 2
+C.ELEMENT_SIZE = 12
+C.EXPANDED_BAR_SIZE = qui.BIG_PADDING + (qui.BIG_PADDING * 2 + C.ELEMENT_SIZE * 4) * 4 + qui.PADDING * 2
+
+return C
diff --git a/.config/awesome/ui/statusbar/init.lua b/.config/awesome/ui/statusbar/init.lua
index 77a28f7..137ffb8 100644
--- a/.config/awesome/ui/statusbar/init.lua
+++ b/.config/awesome/ui/statusbar/init.lua
@@ -1,18 +1,17 @@
local awful = require "awful"
local beautiful = require "beautiful"
local panel = require "ui.statusbar.panel"
-local phosphor = require "assets.phosphor"
-local qstore = require "quarrel.store"
+local qanim = require "quarrel.animation"
local qui = require "quarrel.ui"
local qvars = require "quarrel.vars"
-local rubato = require "lib.rubato"
local wibox = require "wibox"
+local M = require "ui.statusbar.consts"
+
local clock = require "ui.statusbar.widgets.clock"
local displays = require "ui.statusbar.widgets.displays"
local keyboardlayout = require "ui.statusbar.widgets.keyboardlayout"
-local taglist = require "ui.statusbar.widgets.taglist"
-local tasklist = require "ui.statusbar.widgets.tasklist"
+local taglist = require "ui.statusbar.widgets.taglist_new"
screen.connect_signal("request::desktop_decoration", function(s)
local bar = qui.popup {
@@ -21,17 +20,16 @@ screen.connect_signal("request::desktop_decoration", function(s)
margins = beautiful.useless_gap * 2,
})
end,
- minimum_height = s.geometry.height - (beautiful.useless_gap * 4 + qvars.border_width * 2),
+ minimum_height = s.geometry.height - (beautiful.useless_gap * 4 + qui.BORDER_WIDTH * 2),
widget = {
{
{
taglist(s),
layout = wibox.layout.fixed.vertical,
- spacing = qvars.padding * 2,
+ spacing = qui.PADDING * 2,
id = "top",
},
nil,
- -- tasklist(s),
{
widget = wibox.container.place,
valign = "bottom",
@@ -44,14 +42,14 @@ screen.connect_signal("request::desktop_decoration", function(s)
widget = wibox.container.place,
{
widget = wibox.container.constraint,
- height = qvars.char_height,
- width = qvars.char_height,
+ height = qui.CHAR_HEIGHT,
+ width = qui.CHAR_HEIGHT,
keyboardlayout,
},
},
clock,
layout = wibox.layout.fixed.vertical,
- spacing = qvars.padding * 2,
+ spacing = qui.PADDING * 2,
},
},
layout = wibox.layout.align.vertical,
@@ -61,51 +59,47 @@ screen.connect_signal("request::desktop_decoration", function(s)
nil,
layout = wibox.layout.align.horizontal,
},
+
+ toggled = false,
}
- local bar_width = bar.width + qvars.border_width * 2
+ local bar_width = bar.width + qui.BORDER_WIDTH * 2
+
+ bar.shape = function(cr, _, h)
+ qui.shape(cr, bar_width, h)
+ end
bar:struts {
left = bar_width + beautiful.useless_gap * 4,
}
- local panel_width
- local panel_toggle = { toggled = false } -- hacky but it works
+ bar.widget.widget.third = panel
- local timed = rubato.timed {
+ local timed = qanim:new {
duration = qvars.anim_duration,
- intro = qvars.anim_intro,
pos = bar_width,
+ easing = qvars.easing,
subscribed = function(pos)
- if pos ~= bar_width and panel_toggle.toggled then
- bar.widget.widget.third = panel
- if panel_width == nil then
- panel_width = bar.widget.widget.third.width
- end
+ if pos ~= bar_width and bar.toggled then
bar.ontop = true
- elseif pos == bar_width and not panel_toggle.toggled then
- bar.widget.widget.third = nil
+ elseif pos == bar_width and not bar.toggled then
bar.ontop = false
end
bar.shape = function(cr, _, h)
- qvars.shape(cr, pos, h)
+ qui.shape(cr, pos, h)
end
end,
}
- panel_toggle = qui.toggle {
- off = phosphor.arrows_out_simple_fill,
- on = phosphor.arrows_in_simple_fill,
- press = function(self)
- if not self.toggled then
- timed.target = bar_width
- else
- timed.target = bar_width + qvars.expanded_bar_size
- end
- end,
- }
+ function bar:toggle()
+ self.toggled = not self.toggled
+ if self.toggled then
+ timed:set(bar_width + M.EXPANDED_BAR_SIZE)
+ else
+ timed:set(bar_width)
+ end
+ end
- bar.widget:get_children_by_id("top")[1]:insert(1, panel_toggle)
- qstore.panel_toggle = panel_toggle
+ s.bar = bar
end)
diff --git a/.config/awesome/ui/statusbar/panel/init.lua b/.config/awesome/ui/statusbar/panel/init.lua
index 31c2860..814a5a1 100644
--- a/.config/awesome/ui/statusbar/panel/init.lua
+++ b/.config/awesome/ui/statusbar/panel/init.lua
@@ -1,51 +1,61 @@
-local qvars = require "quarrel.vars"
+local C = require "ui.statusbar.consts"
+local qui = require "quarrel.ui"
local wibox = require "wibox"
local battery = require "ui.statusbar.panel.widgets.battery"
local calendar = require "ui.statusbar.panel.widgets.calendar"
local displays = require "ui.statusbar.panel.widgets.displays"
-local music = require "ui.statusbar.panel.widgets.music"
-local power_menu = require "ui.statusbar.panel.widgets.power_menu"
+local music = require "ui.statusbar.panel.widgets.mpris"
local wifi = require "ui.statusbar.panel.widgets.wifi"
local panel = wibox.widget {
- widget = wibox.container.margin,
- margins = {
- left = qvars.big_padding,
- },
+ widget = wibox.container.constraint,
+ strategy = "max",
+ width = C.EXPANDED_BAR_SIZE,
+ -- visible = false,
{
+ widget = wibox.container.background,
{
- widget = wibox.container.place,
- valign = "top",
+ widget = wibox.container.margin,
+ margins = {
+ left = qui.BIG_PADDING,
+ },
{
{
- displays.battery,
- displays.audio,
- displays.brightness,
- layout = wibox.layout.fixed.horizontal,
- spacing = qvars.padding,
+ widget = wibox.container.place,
+ valign = "top",
+ {
+ {
+ displays.battery,
+ displays.audio,
+ -- displays.brightness,
+ -- displays.brightness,
+ layout = wibox.layout.flex.horizontal,
+ spacing = qui.PADDING,
+ },
+ wifi,
+ -- battery,
+ music.widget,
+ calendar,
+ layout = wibox.layout.fixed.vertical,
+ spacing = qui.PADDING,
+ },
},
- wifi,
- -- battery,
- -- music,
- calendar,
- layout = wibox.layout.fixed.vertical,
- spacing = qvars.padding,
- },
- },
- {
- widget = wibox.container.background,
- {
- widget = wibox.widget.textbox,
- text = ":)",
+ -- {
+ -- widget = wibox.container.background,
+ -- {
+ -- widget = wibox.widget.textbox,
+ -- text = ":)",
+ -- },
+ -- },
+ -- {
+ -- widget = wibox.container.place,
+ -- valign = "bottom",
+ -- power_menu,
+ -- },
+ layout = wibox.layout.align.vertical,
},
},
- {
- widget = wibox.container.place,
- valign = "bottom",
- power_menu,
- },
- layout = wibox.layout.align.vertical,
},
}
diff --git a/.config/awesome/ui/statusbar/panel/widgets/battery.lua b/.config/awesome/ui/statusbar/panel/widgets/battery.lua
index 52685b7..52cf5da 100644
--- a/.config/awesome/ui/statusbar/panel/widgets/battery.lua
+++ b/.config/awesome/ui/statusbar/panel/widgets/battery.lua
@@ -1,6 +1,5 @@
local gears = require "gears"
local lit = require "lib.lit"
-local qbezier = require "quarrel.bezier"
local qui = require "quarrel.ui"
local wibox = require "wibox"
diff --git a/.config/awesome/ui/statusbar/panel/widgets/calendar.lua b/.config/awesome/ui/statusbar/panel/widgets/calendar.lua
index 8933543..ddbffc0 100644
--- a/.config/awesome/ui/statusbar/panel/widgets/calendar.lua
+++ b/.config/awesome/ui/statusbar/panel/widgets/calendar.lua
@@ -1,58 +1,123 @@
local awful = require "awful"
local phosphor = require "assets.phosphor"
local qbind = require "quarrel.bind"
+local qcolor = require "quarrel.color"
local qmarkup = require "quarrel.markup"
local qui = require "quarrel.ui"
-local qvars = require "quarrel.vars"
local wibox = require "wibox"
local weekday_map = { 7, 1, 2, 3, 4, 5, 6 }
local calendar = wibox.widget(qui.styled {
widget = wibox.container.background,
+ bg = qcolor.palette.bg.high,
{
widget = wibox.container.margin,
- margins = qvars.big_padding,
+ margins = qui.BORDER_WIDTH,
{
{
{
- widget = wibox.container.place,
- qui.icon {
- icon = phosphor.caret_left_bold,
+ layout = wibox.layout.grid,
+ forced_num_rows = 7,
+ forced_num_cols = 7,
+ -- spacing = (qui.PADDING) / 2,
+ border_width = {
+ inner = qui.BORDER_WIDTH,
+ outer = 0,
},
+ border_color = qcolor.palette.border.variant,
+ id = "grid",
},
{
- widget = wibox.container.place,
- {
- widget = wibox.widget.textbox,
- text = "January 2024",
- },
+ widget = wibox.container.background,
+ bg = qcolor.palette.border(),
+ forced_width = qui.BORDER_WIDTH,
},
+ layout = wibox.layout.fixed.horizontal,
+ },
+ {
+ widget = wibox.container.margin,
+ margins = qui.BIG_PADDING,
{
- widget = wibox.container.place,
- qui.icon {
- icon = phosphor.caret_right_bold,
+ {
+ widget = wibox.container.place,
+ fill_horizontal = true,
+ {
+ widget = wibox.widget.textbox,
+ text = "05\n25",
+ },
},
+ nil,
+ qui.styled {
+ widget = wibox.container.background,
+ bg = qcolor.palette.bg.highest,
+ {
+ {
+ widget = wibox.container.margin,
+ margins = qui.PADDING,
+ {
+ widget = wibox.container.place,
+ qui.icon {
+ icon = phosphor.caret_up_bold,
+ widget = {
+ forced_height = qui.CHAR_HEIGHT / 2,
+ forced_width = qui.CHAR_HEIGHT / 2,
+ },
+ },
+ },
+ },
+ {
+ widget = wibox.container.background,
+ bg = qcolor.palette.border.variant,
+ forced_height = qui.BORDER_WIDTH,
+ },
+ {
+ widget = wibox.container.margin,
+ margins = qui.PADDING,
+ {
+ widget = wibox.container.place,
+ qui.icon {
+ icon = phosphor.caret_down_bold,
+ widget = {
+ forced_height = qui.CHAR_HEIGHT / 2,
+ forced_width = qui.CHAR_HEIGHT / 2,
+ },
+ },
+ },
+ },
+ layout = wibox.layout.fixed.vertical,
+ -- spacing = qui.PADDING
+ },
+ },
+ layout = wibox.layout.align.vertical,
},
- layout = wibox.layout.align.horizontal,
- expand = "outside",
- },
- {
- layout = wibox.layout.grid,
- forced_num_rows = 7,
- forced_num_cols = 7,
- spacing = qvars.padding,
- id = "grid",
},
- layout = wibox.layout.fixed.vertical,
- spacing = qvars.big_padding,
+ layout = wibox.layout.fixed.horizontal,
},
},
})
+local grid = calendar:get_children_by_id("grid")[1]
+
-- Logic heavily inspired by https://github.com/Sinomor/dotfiles/blob/e409f9a84bf40daf1e39c0179ec749232ed827c9/home/.config/awesome/ui/control/moment/calendar.lua#L134-L173
function calendar:compute_grid(year, month)
- local calendar_table = { {}, {}, {}, {}, {}, {} }
+ local calendar_table = {
+ {
+ { "Mo", false, true },
+ { "Tu", false, true },
+ { "We", false, true },
+ { "Th", false, true },
+ { "Fr", false, true },
+ { "Sa", false, true },
+ { "Su", false, true },
+ },
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ }
local current = os.date("*t", os.time())
local first_day = os.date("*t", os.time { year = year, month = month, day = 1 })
local last_day = os.date("*t", os.time { year = year, month = month + 1, day = 0 })
@@ -68,14 +133,14 @@ function calendar:compute_grid(year, month)
for offset = prev_offset, 1, -1 do
local day = prev_days.day - offset + 1
table.insert(
- calendar_table[1],
+ calendar_table[2],
{ day, day == current.day and prev_month == current.month and prev_year == current.year, false }
)
end
end
do
- local row = 1
+ local row = 2
local weekday = weekday_map[first_day.wday]
for day = 1, days do
table.insert(
@@ -108,14 +173,22 @@ function calendar:compute_grid(year, month)
for i, row in ipairs(calendar_table) do
for j, col in ipairs(row) do
- local widget = self:get_children_by_id("grid")[1]:get_widgets_at(i + 1, j)[1]
- widget.widget.widget.text = col[1]
+ local widget = grid:get_widgets_at(i, j)[1]
+ widget.widget.widget.widget.text = col[1]
if col[2] then
- widget.bg = qvars.colors.yellow
- widget.fg = qvars.colors.bg
+ widget.bg = qcolor.palette.yellow()
+ widget.fg = qcolor.palette.bg()
+ elseif j % 6 == 0 or j % 7 == 0 then
+ widget.bg = col[3] and qcolor.palette.bg.highest or qcolor.palette.bg.high
else
- widget.bg = qvars.colors.bg
- widget.fg = col[3] and qvars.colors.fg or qvars.colors.dim.fg
+ widget.bg = col[3] and qcolor.palette.bg.high or qcolor.palette.bg()
+ end
+
+ if i == 1 then
+ widget.fg = qcolor.palette.fg()
+ elseif not col[2] then
+ -- widget.fg = col[3] and qcolor.palette.fg() or qcolor.palette.fg.low
+ widget.fg = qcolor.palette.fg.low
end
end
end
@@ -125,32 +198,31 @@ local cells = {}
local function cell(content)
local widget = wibox.widget {
widget = wibox.container.background,
- shape = qvars.shape,
+ bg = qcolor.palette.bg.high,
+ -- shape = qui.shape,
{
- widget = wibox.container.place,
- forced_height = qvars.char_height * 1.5,
- forced_width = qvars.char_height * 1.5,
+ widget = wibox.container.margin,
+ margins = qui.PADDING * 1.5,
{
- widget = wibox.widget.textbox,
- markup = content,
+ widget = wibox.container.place,
+ forced_height = qui.CHAR_HEIGHT,
+ forced_width = qui.CHAR_HEIGHT,
+ {
+ widget = wibox.widget.textbox,
+ markup = content,
+ },
},
},
}
table.insert(cells, widget)
end
-cell "Mo"
-cell "Tu"
-cell "We"
-cell "Th"
-cell "Fr"
-cell(qmarkup("Sa", { bold = true }))
-cell(qmarkup("Su", { bold = true }))
-
-for _ = 1, 42 do
+for _ = 1, 49 do
cell()
end
-calendar:get_children_by_id("grid")[1]:add(table.unpack(cells))
-calendar:compute_grid(2024, 1)
+grid:add(table.unpack(cells))
+local current_time = os.date "*t"
+calendar:compute_grid(current_time.year, current_time.month)
+grid:add_row_border(2, qui.BORDER_WIDTH, { color = qcolor.palette.border() })
return calendar
diff --git a/.config/awesome/ui/statusbar/panel/widgets/displays.lua b/.config/awesome/ui/statusbar/panel/widgets/displays.lua
index b52c986..bfb9113 100644
--- a/.config/awesome/ui/statusbar/panel/widgets/displays.lua
+++ b/.config/awesome/ui/statusbar/panel/widgets/displays.lua
@@ -1,37 +1,43 @@
-local backlight = require "services.backlight"
+-- local backlight = require "services.backlight"
local battery = require "services.battery"
local phosphor = require "assets.phosphor"
-local qmath = require "quarrel.math"
+-- local qmath = require "quarrel.math"
+local C = require "ui.statusbar.consts"
+local qcolor = require "quarrel.color"
local qui = require "quarrel.ui"
-local qvars = require "quarrel.vars"
local wibox = require "wibox"
local function create_display(icon, color)
+ -- the reason this is done is because ids can't be found with `get_children_by_id` across widget boundaries,
+ -- so we return it as a second value in order to manipulate the icon
+ local d_icon = qui.icon {
+ icon = icon,
+ color = color,
+ widget = {
+ id = "icon",
+ },
+ }
+
return wibox.widget(qui.styled {
widget = wibox.container.background,
+ bg = qcolor.palette.bg.high,
{
widget = wibox.container.margin,
- margins = qvars.big_padding,
+ margins = qui.BIG_PADDING,
{
widget = wibox.container.place,
{
{
widget = wibox.container.radialprogressbar,
max_value = 100,
- border_color = qvars.colors.black,
+ border_color = qcolor.palette.bg.lowest,
color = color,
- border_width = qvars.border_width * 2,
- forced_height = qvars.element_size * 4,
- forced_width = qvars.element_size * 4,
+ border_width = qui.BORDER_WIDTH * 2,
+ forced_height = C.ELEMENT_SIZE * 4,
+ forced_width = C.ELEMENT_SIZE * 4,
{
widget = wibox.container.place,
- qui.icon {
- icon = icon,
- color = color,
- widget = {
- id = "icon",
- },
- },
+ d_icon,
},
id = "indicator",
},
@@ -44,14 +50,15 @@ local function create_display(icon, color)
},
},
layout = wibox.layout.fixed.vertical,
- spacing = qvars.big_padding,
+ spacing = qui.BIG_PADDING,
},
},
},
- })
+ }),
+ d_icon
end
-local d_audio = create_display(phosphor.speaker_simple_high_fill, qvars.colors.fg)
+local d_audio, d_audio_icon = create_display(phosphor.speaker_simple_high_fill, qcolor.palette.fg())
awesome.connect_signal("services::audio", function(volume)
if not volume then
return
@@ -61,30 +68,33 @@ awesome.connect_signal("services::audio", function(volume)
end)
awesome.connect_signal("services::audio::icon", function(icon, color)
d_audio:get_children_by_id("indicator")[1].color = color
- d_audio:get_children_by_id("icon")[1].image = icon
- d_audio:get_children_by_id("icon")[1].stylesheet = qui.recolor(color)
+ d_audio_icon.image = icon
+ d_audio_icon.stylesheet = qui.recolor(color)
end)
-local d_battery = create_display(phosphor.battery_vertical_warning_fill, qvars.colors.red)
+local d_battery, d_battery_icon = create_display(phosphor.battery_vertical_warning_fill, qcolor.palette.red())
battery:connect_signal("level", function(_, level)
d_battery:get_children_by_id("indicator")[1].value = level
d_battery:get_children_by_id("text")[1].text = level .. "%"
end)
battery:connect_signal("icon", function(_, icon, color)
d_battery:get_children_by_id("indicator")[1].color = color
- d_battery:get_children_by_id("icon")[1].image = icon
- d_battery:get_children_by_id("icon")[1].stylesheet = qui.recolor(color)
+ d_battery_icon.image = icon
+ d_battery_icon.stylesheet = qui.recolor(color)
end)
-local d_brightness = create_display(phosphor.sun_fill, qvars.colors.fg)
-backlight:connect_signal("value", function(_, brightness)
- brightness = math.floor(qmath.translate_range(brightness, 0, 255, 0, 100))
- d_brightness:get_children_by_id("indicator")[1].value = brightness
- d_brightness:get_children_by_id("text")[1].text = brightness .. "%"
-end)
-backlight:connect_signal("icon", function(_, icon, color)
- d_brightness:get_children_by_id("icon")[1].image = icon
- d_brightness:get_children_by_id("icon")[1].stylesheet = qui.recolor(color)
-end)
+-- local d_brightness, d_brightness_icon = create_display(phosphor.sun_fill, qcolor.palette.fg())
+-- backlight:connect_signal("value", function(_, brightness)
+-- brightness = math.floor(qmath.translate_range(brightness, 0, 255, 0, 100))
+-- d_brightness:get_children_by_id("indicator")[1].value = brightness
+-- d_brightness:get_children_by_id("text")[1].text = brightness .. "%"
+-- end)
+-- backlight:connect_signal("icon", function(_, icon, color)
+-- d_brightness_icon.image = icon
+-- d_brightness_icon.stylesheet = qui.recolor(color)
+-- end)
-return { audio = d_audio, battery = d_battery, brightness = d_brightness }
+return {
+ audio = d_audio,
+ battery = d_battery, --[[brightness = d_brightness]]
+}
diff --git a/.config/awesome/ui/statusbar/panel/widgets/imagebox.lua b/.config/awesome/ui/statusbar/panel/widgets/imagebox.lua
deleted file mode 100644
index 5caadc5..0000000
--- a/.config/awesome/ui/statusbar/panel/widgets/imagebox.lua
+++ /dev/null
@@ -1,720 +0,0 @@
----------------------------------------------------------------------------
--- A widget to display an image.
---
--- The `wibox.widget.imagebox` is part of the Awesome WM's widget system
--- (see @{03-declarative-layout.md}).
---
--- This widget displays an image. The image can be a file,
--- a cairo image surface, or an rsvg handle object (see the
--- [image property](#image)).
---
--- Examples using a `wibox.widget.imagebox`:
--- ---
---
--- @DOC_wibox_widget_defaults_imagebox_EXAMPLE@
---
--- Alternatively, you can declare the `imagebox` widget using the
--- declarative pattern (both variants are strictly equivalent):
---
--- @DOC_wibox_widget_declarative-pattern_imagebox_EXAMPLE@
---
--- @author Uli Schlachter
--- @copyright 2010 Uli Schlachter
--- @widgetmod wibox.widget.imagebox
--- @supermodule wibox.widget.base
----------------------------------------------------------------------------
-
-local lgi = require "lgi"
-local cairo = lgi.cairo
-
-local base = require "wibox.widget.base"
-local gdebug = require "gears.debug"
-local gtable = require "gears.table"
-local surface = require "gears.surface"
-local setmetatable = setmetatable
-local type = type
-local math = math
-
-local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
-
--- Safe load for optional Rsvg module
-local Rsvg = nil
-do
- local success, err = pcall(function()
- Rsvg = lgi.Rsvg
- end)
- if not success then
- gdebug.print_warning(debug.traceback("Could not load Rsvg: " .. tostring(err)))
- end
-end
-
-local imagebox = { mt = {} }
-
-local rsvg_handle_cache = setmetatable({}, { __mode = "k" })
-
----Load rsvg handle form image file
--- @tparam string file Path to svg file.
--- @return Rsvg handle
--- @treturn table A table where cached data can be stored.
-local function load_rsvg_handle(file)
- if not Rsvg then
- return
- end
-
- local cache = (rsvg_handle_cache[file] or {})["handle"]
-
- if cache then
- return cache, rsvg_handle_cache[file]
- end
-
- local handle, err
-
- if file:match "<[?]?xml" or file:match "<svg" then
- handle, err = Rsvg.Handle.new_from_data(file)
- else
- handle, err = Rsvg.Handle.new_from_file(file)
- end
-
- if not err then
- rsvg_handle_cache[file] = rsvg_handle_cache[file] or {}
- rsvg_handle_cache[file]["handle"] = handle
- return handle, rsvg_handle_cache[file]
- end
-end
-
----Apply cairo surface for given imagebox widget
-local function set_surface(ib, surf)
- local is_surf_valid = surf.width > 0 and surf.height > 0
- if not is_surf_valid then
- return false
- end
-
- ib._private.default = { width = surf.width, height = surf.height }
- ib._private.handle = nil
- ib._private.image = surf
- return true
-end
-
----Apply RsvgHandle for given imagebox widget
-local function set_handle(ib, handle, cache)
- local dim = handle:get_dimensions()
- local is_handle_valid = dim.width > 0 and dim.height > 0
- if not is_handle_valid then
- return false
- end
-
- ib._private.default = { width = dim.width, height = dim.height }
- ib._private.handle = handle
- ib._private.cache = cache
- ib._private.image = nil
-
- return true
-end
-
----Try to load some image object from file then apply it to imagebox.
----@tparam table ib Imagebox
----@tparam string file Image file name
----@tparam function image_loader Function to load image object from file
----@tparam function image_setter Function to set image object to imagebox
----@treturn boolean True if image was successfully applied
-local function load_and_apply(ib, file, image_loader, image_setter)
- local image_applied
- local object, cache = image_loader(file)
-
- if object then
- image_applied = image_setter(ib, object, cache)
- end
- return image_applied
-end
-
----Update the cached size depending on the stylesheet and dpi.
---
--- It's necessary because a single RSVG handle can be used by
--- many imageboxes. So DPI and Stylesheet need to be set each time.
-local function update_dpi(self, ctx)
- if not self._private.handle then
- return
- end
-
- local dpi = self._private.auto_dpi and ctx.dpi or self._private.dpi or nil
-
- local need_dpi = dpi and self._private.last_dpi ~= dpi
-
- local need_style = self._private.handle.set_stylesheet and self._private.stylesheet
-
- local old_size = self._private.default and self._private.default.width
-
- if dpi and dpi ~= self._private.cache.dpi then
- if type(dpi) == "table" then
- self._private.handle:set_dpi_x_y(dpi.x, dpi.y)
- else
- self._private.handle:set_dpi(dpi)
- end
- end
-
- if need_style and self._private.cache.stylesheet ~= self._private.stylesheet then
- self._private.handle:set_stylesheet(self._private.stylesheet)
- end
-
- -- Reload the size.
- if need_dpi or (need_style and self._private.stylesheet ~= self._private.last_stylesheet) then
- set_handle(self, self._private.handle, self._private.cache)
- end
-
- self._private.last_dpi = dpi
- self._private.cache.dpi = dpi
- self._private.last_stylesheet = self._private.stylesheet
- self._private.cache.stylesheet = self._private.stylesheet
-
- -- This can happen in the constructor when `dpi` is set after `image`.
- if old_size and old_size ~= self._private.default.width then
- self:emit_signal "widget::redraw_needed"
- self:emit_signal "widget::layout_changed"
- end
-end
-
--- Draw an imagebox with the given cairo context in the given geometry.
-function imagebox:draw(ctx, cr, width, height)
- if width == 0 or height == 0 or not self._private.default then
- return
- end
-
- -- For valign = "top" and halign = "left"
- local translate = {
- x = 0,
- y = 0,
- }
-
- update_dpi(self, ctx)
-
- local w, h = self._private.default.width, self._private.default.height
-
- if self._private.resize then
- -- That's for the "fit" policy.
- local aspects = {
- w = width / w,
- h = height / h,
- }
-
- local policy = {
- w = self._private.horizontal_fit_policy or "auto",
- h = self._private.vertical_fit_policy or "auto",
- }
-
- for _, aspect in ipairs { "w", "h" } do
- if self._private.upscale == false and (w < width and h < height) then
- aspects[aspect] = 1
- elseif self._private.downscale == false and (w >= width and h >= height) then
- aspects[aspect] = 1
- elseif policy[aspect] == "none" then
- aspects[aspect] = 1
- elseif policy[aspect] == "auto" then
- aspects[aspect] = math.min(width / w, height / h)
- elseif policy[aspect] == "cover" then
- aspects[aspect] = math.max(width / w, height / h)
- end
- end
-
- if self._private.halign == "center" then
- translate.x = math.floor((width - w * aspects.w) / 2)
- elseif self._private.halign == "right" then
- translate.x = math.floor(width - (w * aspects.w))
- end
-
- if self._private.valign == "center" then
- translate.y = math.floor((height - h * aspects.h) / 2)
- elseif self._private.valign == "bottom" then
- translate.y = math.floor(height - (h * aspects.h))
- end
-
- cr:translate(translate.x, translate.y)
-
- -- Before using the scale, make sure it is below the threshold.
- local threshold, max_factor = self._private.max_scaling_factor, math.max(aspects.w, aspects.h)
-
- if threshold and threshold > 0 and threshold < max_factor then
- aspects.w = (aspects.w * threshold) / max_factor
- aspects.h = (aspects.h * threshold) / max_factor
- end
-
- -- Set the clip
- if self._private.clip_shape then
- cr:clip(self._private.clip_shape(cr, w * aspects.w, h * aspects.h, unpack(self._private.clip_args)))
- end
-
- cr:scale(aspects.w, aspects.h)
- else
- if self._private.halign == "center" then
- translate.x = math.floor((width - w) / 2)
- elseif self._private.halign == "right" then
- translate.x = math.floor(width - w)
- end
-
- if self._private.valign == "center" then
- translate.y = math.floor((height - h) / 2)
- elseif self._private.valign == "bottom" then
- translate.y = math.floor(height - h)
- end
-
- cr:translate(translate.x, translate.y)
-
- -- Set the clip
- if self._private.clip_shape then
- cr:clip(self._private.clip_shape(cr, w, h, unpack(self._private.clip_args)))
- end
- end
-
- if self._private.handle then
- self._private.handle:render_cairo(cr)
- else
- cr:set_source_surface(self._private.image, 0, 0)
-
- local filter = self._private.scaling_quality
-
- if filter then
- cr:get_source():set_filter(cairo.Filter[filter:upper()])
- end
-
- cr:paint()
- end
-end
-
--- Fit the imagebox into the given geometry
-function imagebox:fit(ctx, width, height)
- if not self._private.default then
- return 0, 0
- end
-
- update_dpi(self, ctx)
-
- local w, h = self._private.default.width, self._private.default.height
-
- if w <= width and h <= height and self._private.upscale == false then
- return w, h
- end
-
- if (w < width or h < height) and self._private.downscale == false then
- return w, h
- end
-
- if self._private.resize or w > width or h > height then
- local aspect = math.min(width / w, height / h)
- return w * aspect, h * aspect
- end
-
- return w, h
-end
-
---- The image rendered by the `imagebox`.
---
--- @property image
--- @tparam[opt=nil] image|nil image
--- @propemits false false
-
---- Set the `imagebox` image.
---
--- The image can be a file, a cairo image surface, or an rsvg handle object
--- (see the [image property](#image)).
--- @method set_image
--- @hidden
--- @tparam image image The image to render.
--- @treturn boolean `true` on success, `false` if the image cannot be used.
--- @usage my_imagebox:set_image(beautiful.awesome_icon)
--- @usage my_imagebox:set_image('/usr/share/icons/theme/my_icon.png')
--- @see image
-function imagebox:set_image(image)
- local setup_succeed
-
- -- Keep the original to prevent the cache from being GCed.
- self._private.original_image = image
-
- if type(image) == "userdata" and not (Rsvg and Rsvg.Handle:is_type_of(image)) then
- -- This function is not documented to handle userdata objects, but
- -- historically it did, and it did by just assuming they refer to a
- -- cairo surface.
- image = surface.load(image)
- end
-
- if type(image) == "string" then
- -- try to load rsvg handle from file
- setup_succeed = load_and_apply(self, image, load_rsvg_handle, set_handle)
-
- if not setup_succeed then
- -- rsvg handle failed, try to load cairo surface with pixbuf
- setup_succeed = load_and_apply(self, image, surface.load, set_surface)
- end
- elseif Rsvg and Rsvg.Handle:is_type_of(image) then
- -- try to apply given rsvg handle
- rsvg_handle_cache[image] = rsvg_handle_cache[image] or {}
- setup_succeed = set_handle(self, image, rsvg_handle_cache[image])
- elseif cairo.Surface:is_type_of(image) then
- -- try to apply given cairo surface
- setup_succeed = set_surface(self, image)
- elseif not image then
- -- nil as argument mean full imagebox reset
- setup_succeed = true
- self._private.handle = nil
- self._private.image = nil
- self._private.default = nil
- end
-
- if not setup_succeed then
- return false
- end
-
- self:emit_signal "widget::redraw_needed"
- self:emit_signal "widget::layout_changed"
- self:emit_signal "property::image"
- return true
-end
-
---- Set a clip shape for this imagebox.
---
--- A clip shape defines an area and dimension to which the content should be
--- trimmed.
---
--- @DOC_wibox_widget_imagebox_clip_shape_EXAMPLE@
---
--- @property clip_shape
--- @tparam[opt=gears.shape.rectangle] shape clip_shape A `gears.shape` compatible shape function.
--- @propemits true false
--- @see gears.shape
-
---- Set a clip shape for this imagebox.
---
--- A clip shape defines an area and dimensions to which the content should be
--- trimmed.
---
--- Additional parameters will be passed to the clip shape function.
---
--- @tparam function|gears.shape clip_shape A `gears_shape` compatible shape function.
--- @method set_clip_shape
--- @hidden
--- @see gears.shape
--- @see clip_shape
-function imagebox:set_clip_shape(clip_shape, ...)
- self._private.clip_shape = clip_shape
- self._private.clip_args = { ... }
- self:emit_signal "widget::redraw_needed"
- self:emit_signal("property::clip_shape", clip_shape)
-end
-
---- Should the image be resized to fit into the available space?
---
--- Note that `upscale` and `downscale` can affect the value of `resize`.
--- If conflicting values are passed to the constructor, then the result
--- is undefined.
---
--- @DOC_wibox_widget_imagebox_resize_EXAMPLE@
--- @property resize
--- @propemits true false
--- @tparam[opt=true] boolean resize
-
---- Allow the image to be upscaled (made bigger).
---
--- Note that `upscale` and `downscale` can affect the value of `resize`.
--- If conflicting values are passed to the constructor, then the result
--- is undefined.
---
--- @DOC_wibox_widget_imagebox_upscale_EXAMPLE@
--- @property upscale
--- @tparam[opt=self.resize] boolean upscale
--- @see downscale
--- @see resize
-
---- Allow the image to be downscaled (made smaller).
---
--- Note that `upscale` and `downscale` can affect the value of `resize`.
--- If conflicting values are passed to the constructor, then the result
--- is undefined.
---
--- @DOC_wibox_widget_imagebox_downscale_EXAMPLE@
--- @property downscale
--- @tparam[opt=self.resize] boolean downscale
--- @see upscale
--- @see resize
-
---- Set the SVG CSS stylesheet.
---
--- If the image is an SVG (vector graphics), this property allows to set
--- a CSS stylesheet. It can be used to set colors and much more.
---
--- Note that this property is a string, not a path. If the stylesheet is
--- stored on disk, read the content first.
---
---@DOC_wibox_widget_imagebox_stylesheet_EXAMPLE@
---
--- @property stylesheet
--- @tparam[opt=""] string stylesheet
--- @propemits true false
-
---- Set the SVG DPI (dot per inch).
---
--- Force a specific DPI when rendering the `.svg`. For other file formats,
--- this does nothing.
---
--- It can either be a number of a table containing the `x` and `y` keys.
---
--- Please note that DPI and `resize` can "fight" each other and end up
--- making the image smaller instead of bigger.
---
---@DOC_wibox_widget_imagebox_dpi_EXAMPLE@
---
--- @property dpi
--- @tparam[opt=96] number|table dpi
--- @negativeallowed false
--- @propemits true false
--- @see auto_dpi
-
---- Use the object DPI when rendering the SVG.
---
--- By default, the SVG are interpreted as-is. When this property is set,
--- the screen DPI will be passed to the SVG renderer. Depending on which
--- tool was used to create the `.svg`, this may do nothing at all. However,
--- for example, if the `.svg` uses `<text>` elements and doesn't have an
--- hardcoded stylesheet, the result will differ.
---
--- @property auto_dpi
--- @tparam[opt=false] boolean auto_dpi
--- @propemits true false
--- @see dpi
-
-for _, prop in ipairs { "stylesheet", "dpi", "auto_dpi" } do
- imagebox["set_" .. prop] = function(self, value)
- -- It will be set in :fit and :draw. The handle is shared
- -- by multiple imagebox, so it cannot be set just once.
- self._private[prop] = value
-
- self:emit_signal "widget::redraw_needed"
- self:emit_signal "widget::layout_changed"
- self:emit_signal("property::" .. prop)
- end
-end
-
-function imagebox:set_resize(allowed)
- self._private.resize = allowed
-
- if allowed then
- self._private.downscale = true
- self._private.upscale = true
- self:emit_signal("property::downscale", allowed)
- self:emit_signal("property::upscale", allowed)
- end
-
- self:emit_signal "widget::redraw_needed"
- self:emit_signal "widget::layout_changed"
- self:emit_signal("property::resize", allowed)
-end
-
-for _, prop in ipairs { "downscale", "upscale" } do
- imagebox["set_" .. prop] = function(self, allowed)
- self._private[prop] = allowed
-
- if self._private.resize ~= (self._private.upscale or self._private.downscale) then
- self._private.resize = self._private.upscale or self._private.downscale
- self:emit_signal("property::resize", self._private.resize)
- end
-
- self:emit_signal "widget::redraw_needed"
- self:emit_signal "widget::layout_changed"
- self:emit_signal("property::" .. prop, allowed)
- end
-end
-
---- Set the horizontal fit policy.
---
--- Here is the result for a 22x32 image:
---
--- @DOC_wibox_widget_imagebox_horizontal_fit_policy_EXAMPLE@
---
--- @property horizontal_fit_policy
--- @tparam[opt="auto"] string horizontal_fit_policy
--- @propertyvalue "auto" Honor the `resize` variable and preserve the aspect ratio.
--- @propertyvalue "none" Do not resize at all.
--- @propertyvalue "fit" Resize to the widget width.
--- @propertyvalue "cover" Resize to fill widget and preserve the aspect ratio.
--- @propemits true false
--- @see vertical_fit_policy
--- @see resize
-
---- Set the vertical fit policy.
---
--- Here is the result for a 32x22 image:
---
--- @DOC_wibox_widget_imagebox_vertical_fit_policy_EXAMPLE@
---
--- @property vertical_fit_policy
--- @tparam[opt="auto"] string vertical_fit_policy
--- @propertyvalue "auto" Honor the `resize` variable and preserve the aspect ratio.
--- @propertyvalue "none" Do not resize at all.
--- @propertyvalue "fit" Resize to the widget height.
--- @propertyvalue "cover" Resize to fill widget and preserve the aspect ratio.
--- @propemits true false
--- @see horizontal_fit_policy
--- @see resize
-
---- The vertical alignment.
---
--- @DOC_wibox_widget_imagebox_valign_EXAMPLE@
---
--- @property valign
--- @tparam[opt="center"] string valign
--- @propertyvalue "top"
--- @propertyvalue "center"
--- @propertyvalue "bottom"
--- @propemits true false
--- @see wibox.container.place
--- @see halign
-
---- The horizontal alignment.
---
--- @DOC_wibox_widget_imagebox_halign_EXAMPLE@
---
--- @property halign
--- @tparam[opt="center"] string halign
--- @propertyvalue "left"
--- @propertyvalue "center"
--- @propertyvalue "right"
--- @propemits true false
--- @see wibox.container.place
--- @see valign
-
---- The maximum scaling factor.
---
--- If an image is scaled too much, it gets very blurry. This
--- property allows to limit the scaling.
--- Use the properties `valign` and `halign` to control how the image will be
--- aligned.
---
--- In the example below, the original size is 22x22
---
--- @DOC_wibox_widget_imagebox_max_scaling_factor_EXAMPLE@
---
--- @property max_scaling_factor
--- @tparam[opt=0] number max_scaling_factor Use `0` for "no limit".
--- @negativeallowed false
--- @propemits true false
--- @see valign
--- @see halign
--- @see scaling_quality
-
---- Set the scaling aligorithm.
---
--- Depending on how the image is used, what is the "correct" way to
--- scale can change. For example, upscaling a pixel art image should
--- not make it blurry. However, scaling up a photo should not make it
--- blocky.
---
---<table class='widget_list' border=1>
--- <tr style='font-weight: bold;'>
--- <th align='center'>Value</th>
--- <th align='center'>Description</th>
--- </tr>
--- <tr><td>fast</td><td>A high-performance filter</td></tr>
--- <tr><td>good</td><td>A reasonable-performance filter</td></tr>
--- <tr><td>best</td><td>The highest-quality available</td></tr>
--- <tr><td>nearest</td><td>Nearest-neighbor filtering (blocky)</td></tr>
--- <tr><td>bilinear</td><td>Linear interpolation in two dimensions</td></tr>
---</table>
---
--- The image used in the example below has a resolution of 32x22 and is
--- intentionally blocky to highlight the difference.
--- It is zoomed by a factor of 3.
---
--- @DOC_wibox_widget_imagebox_scaling_quality_EXAMPLE@
---
--- @property scaling_quality
--- @tparam[opt="good"] string scaling_quality
--- @propertyvalue "fast" A high-performance filter.
--- @propertyvalue "good" A reasonable-performance filter.
--- @propertyvalue "best" The highest-quality available.
--- @propertyvalue "nearest" Nearest-neighbor filtering (blocky).
--- @propertyvalue "bilinear" Linear interpolation in two dimensions.
--- @propemits true false
--- @see resize
--- @see horizontal_fit_policy
--- @see vertical_fit_policy
--- @see max_scaling_factor
-
-local defaults = {
- halign = "left",
- valign = "top",
- horizontal_fit_policy = "auto",
- vertical_fit_policy = "auto",
- max_scaling_factor = 0,
- scaling_quality = "good",
-}
-
-local function get_default(prop, value)
- if value == nil then
- return defaults[prop]
- end
-
- return value
-end
-
-for prop in pairs(defaults) do
- imagebox["set_" .. prop] = function(self, value)
- if value == self._private[prop] then
- return
- end
-
- self._private[prop] = get_default(prop, value)
- self:emit_signal "widget::redraw_needed"
- self:emit_signal("property::" .. prop, self._private[prop])
- end
-
- imagebox["get_" .. prop] = function(self)
- if self._private[prop] == nil then
- return defaults[prop]
- end
-
- return self._private[prop]
- end
-end
-
---- Returns a new `wibox.widget.imagebox` instance.
---
--- This is the constructor of `wibox.widget.imagebox`. It creates a new
--- instance of imagebox widget.
---
--- Alternatively, the declarative layout syntax can handle
--- `wibox.widget.imagebox` instanciation.
---
--- The image can be a file, a cairo image surface, or an rsvg handle object
--- (see the [image property](#image)).
---
--- Any additional arguments will be passed to the clip shape function.
--- @tparam[opt] image image The image to display (may be `nil`).
--- @tparam[opt] boolean resize_allowed If `false`, the image will be
--- clipped, else it will be resized to fit into the available space.
--- @tparam[opt] function clip_shape A `gears.shape` compatible function.
--- @treturn wibox.widget.imagebox A new `wibox.widget.imagebox` widget instance.
--- @constructorfct wibox.widget.imagebox
-local function new(image, resize_allowed, clip_shape, ...)
- local ret = base.make_widget(nil, nil, { enable_properties = true })
-
- gtable.crush(ret, imagebox, true)
- ret._private.resize = true
-
- if image then
- ret:set_image(image)
- end
-
- if resize_allowed ~= nil then
- ret.resize = resize_allowed
- end
-
- ret._private.clip_shape = clip_shape
- ret._private.clip_args = { ... }
-
- return ret
-end
-
-function imagebox.mt:__call(...)
- return new(...)
-end
-
-return setmetatable(imagebox, imagebox.mt)
-
--- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/.config/awesome/ui/statusbar/panel/widgets/mpris.lua b/.config/awesome/ui/statusbar/panel/widgets/mpris.lua
new file mode 100644
index 0000000..c27bf66
--- /dev/null
+++ b/.config/awesome/ui/statusbar/panel/widgets/mpris.lua
@@ -0,0 +1,429 @@
+local custom = require "assets.custom"
+local gdebug = require "gears.debug"
+local phosphor = require "assets.phosphor"
+local playerctl = require "services.playerctl"
+local qcolor = require "quarrel.color"
+local qpersistent = require "quarrel.persistent"
+local qui = require "quarrel.ui"
+local qvars = require "quarrel.vars"
+local simpleicons = require "assets.simpleicons"
+local wibox = require "wibox"
+
+local M = {}
+
+local client_icons = { -- this is used to map the mpris clients to icons
+ firefox = simpleicons.librewolf, -- librewolf uses the same player id as firefox
+ spotify = simpleicons.spotify,
+ mpd = custom.vinyl_record_fill,
+ __generic = phosphor.waveform_fill, -- used for any client not in the map
+}
+
+local DEFAULTS = {
+ ---@type string
+ title = "Nothing's playing",
+ artist = {},
+ ---@type string
+ album = "",
+ progresstext = {
+ ---@type string
+ position = "~",
+ ---@type string
+ length = "~",
+ },
+ ---@type number
+ position = 0,
+ ---@type number
+ length = math.huge,
+ client_icon = client_icons.__generic,
+ art = phosphor.music_notes_fill,
+}
+
+local bg_icon = qui.icon {
+ icon = DEFAULTS.client_icon,
+ widget = {
+ forced_width = 0,
+ forced_height = 0,
+ },
+ color = qcolor.palette.bg.highest,
+}
+
+local function to_hms(time)
+ local format = "%i:%02i"
+
+ local h = math.floor(time / 60 ^ 2)
+ local m = math.floor((time / 60) % 60)
+ local s = math.floor(time % 60)
+
+ if h > 0 then
+ format = "%i:" .. format
+ return string.format(format, h, m, s)
+ end
+
+ return string.format(format, m, s)
+end
+
+local function is_empty(str)
+ return (str == nil or #str == 0)
+end
+
+--- mirror of playerctl:list()
+--- why bother mirroring? simple: we need to track changes to the player list
+--- but vanished and appeared simply don't give us an old version to compare with
+---@type Playerctl.data[]
+local players = {}
+
+---@type number
+M.active_player_index = qpersistent.get "active_player_index" --[[@as number]]
+if not M.active_player_index then
+ M.active_player_index = 1
+ gdebug.print_error "failed to get active_player_index from qpersistent, falling back..."
+ qpersistent.store("active_player_index", M.active_player_index)
+end
+
+--- the reason we do this instead of some other hack is this is the only way to draw the icons *without* resizing the mpris container
+--- this happens because in the panel, height is unlimited, and imagebox grows until a hard limit
+---@class wibox.widget.base
+local client_background = wibox.container.background()
+
+function client_background:before_draw_children(_, cr, width, height)
+ cr:save()
+ cr:translate(width - (height / 1.25), -(height * 0.125))
+ ---@diagnostic disable-next-line: missing-parameter
+ wibox.widget.draw_to_cairo_context(bg_icon, cr, height * 1.25, height * 1.25)
+ cr:restore()
+end
+
+M.widget = wibox.widget(qui.styled {
+ widget = wibox.container.background,
+ bg = qcolor.palette.bg.high,
+ {
+ widget = client_background,
+ {
+ widget = wibox.container.margin,
+ margins = qui.BIG_PADDING,
+ {
+ nil,
+ {
+ {
+ {
+ widget = wibox.container.background,
+ bg = qcolor.palette.bg(),
+ shape = qui.shape,
+ {
+ widget = wibox.widget.imagebox,
+ image = DEFAULTS.art,
+ forced_height = qui.CHAR_HEIGHT * 5,
+ forced_width = qui.CHAR_HEIGHT * 5,
+ valign = "center",
+ halign = "center",
+ stylesheet = qui.recolor(qcolor.palette.bg.highest),
+ id = "cover",
+ },
+ },
+ {
+ widget = wibox.container.margin,
+ right = qui.BIG_PADDING,
+ left = qui.BIG_PADDING,
+ {
+ {
+ widget = wibox.container.constraint,
+ height = qui.CHAR_HEIGHT * 2.5,
+ strategy = "max",
+ {
+ widget = wibox.widget.textbox,
+ text = DEFAULTS.title, -- Song
+ id = "song",
+ valign = "top",
+ },
+ },
+ {
+ widget = wibox.container.constraint,
+ height = qui.CHAR_HEIGHT * 2.5,
+ strategy = "max",
+ {
+ widget = wibox.container.background,
+ fg = qcolor.palette.fg.low,
+ {
+ widget = wibox.widget.textbox,
+ text = DEFAULTS.artist_album, -- Artist - Album Name
+ id = "artist_album",
+ valign = "top",
+ },
+ },
+ },
+ layout = wibox.layout.fixed.vertical,
+ },
+ },
+ layout = wibox.layout.fixed.horizontal,
+ },
+ nil,
+ {
+ widget = wibox.container.margin,
+ top = qui.BIG_PADDING,
+ right = qui.BIG_PADDING,
+ {
+ {
+ widget = wibox.widget.textbox,
+ text = DEFAULTS.progresstext.position .. " / " .. DEFAULTS.progresstext.length, -- position / length
+ id = "progresstext",
+ },
+ {
+ widget = wibox.container.place,
+ {
+ widget = wibox.widget.progressbar,
+ forced_height = qui.PADDING,
+ color = qcolor.palette.yellow(),
+ value = DEFAULTS.position,
+ max_value = DEFAULTS.length,
+ background_color = qcolor.palette.bg.lowest,
+ bar_shape = qui.shape,
+ shape = qui.shape,
+ id = "progressbar",
+ },
+ },
+ layout = wibox.layout.fixed.horizontal,
+ spacing = qui.BIG_PADDING,
+ },
+ },
+ layout = wibox.layout.align.vertical,
+ },
+ {
+ layout = wibox.layout.flex.vertical,
+ spacing = qui.BIG_PADDING,
+ id = "client_list",
+ },
+ layout = wibox.layout.align.horizontal,
+ },
+ },
+ },
+})
+
+local layout = M.widget:get_children_by_id("client_list")[1] --[[@as wibox.layout.flex]]
+local progressbar = M.widget:get_children_by_id("progressbar")[1] --[[@as wibox.widget.progressbar]]
+local progresstext = M.widget:get_children_by_id("progresstext")[1] --[[@as wibox.widget.textbox]]
+local song = M.widget:get_children_by_id("song")[1] --[[@as wibox.widget.textbox]]
+local artist_album = M.widget:get_children_by_id("artist_album")[1] --[[@as wibox.widget.textbox]]
+local cover = M.widget:get_children_by_id("cover")[1] --[[@as wibox.widget.imagebox]]
+
+local function mirror_player_list()
+ players = playerctl:list()
+end
+
+mirror_player_list()
+
+local function handle_metadata(_, player)
+ local title, album, artist, art
+ if player ~= playerctl:list()[M.active_player_index] then
+ return
+ end
+ if player then
+ if player.metadata.title then
+ title = player.metadata.title
+ else
+ title = DEFAULTS.title
+ end
+
+ if player.metadata.album then
+ album = player.metadata.album
+ else
+ album = DEFAULTS.album
+ end
+
+ if player.metadata.artist then
+ artist = player.metadata.artist
+ else
+ artist = DEFAULTS.artist
+ end
+
+ if player.metadata.art and player.metadata.art:match "^file://" then
+ art = player.metadata.art:gsub("^file://", "")
+ else
+ art = DEFAULTS.art
+ end
+ else
+ title = DEFAULTS.title
+ album = DEFAULTS.album
+ artist = DEFAULTS.artist
+ art = DEFAULTS.art
+ end
+
+ artist_album.text = table.concat(artist, ", ") .. ((is_empty(artist) or is_empty(album)) and "" or " - ") .. album
+ song.text = title
+ ---@diagnostic disable-next-line:inject-field
+ cover.image = art
+end
+
+local function handle_position(_, player)
+ if player ~= playerctl:list()[M.active_player_index] then
+ return
+ end
+ local position, length
+ local content = ""
+
+ if player then
+ if player.position then
+ position = player.position / playerctl.unit
+ content = content .. to_hms(position)
+ else
+ position = DEFAULTS.position
+ content = content .. DEFAULTS.progresstext.position
+ end
+
+ content = content .. " / "
+
+ if player.metadata.length then
+ length = player.metadata.length / playerctl.unit
+ content = content .. to_hms(length)
+ else
+ length = DEFAULTS.length
+ content = content .. DEFAULTS.progresstext.length
+ end
+ else
+ position = DEFAULTS.position
+ length = DEFAULTS.length
+ content = DEFAULTS.progresstext.position .. " / " .. DEFAULTS.progresstext.length
+ end
+
+ progresstext.text = content
+ ---@diagnostic disable-next-line:inject-field
+ progressbar.value = position
+ ---@diagnostic disable-next-line:inject-field
+ progressbar.max_value = length
+end
+
+playerctl:connect_signal("player::metadata", handle_metadata)
+playerctl:connect_signal("player::position", handle_position)
+
+local function update_player(player)
+ handle_metadata(nil, player)
+ handle_position(nil, player)
+ qpersistent.store("active_player_index", M.active_player_index)
+
+ local client_icon = client_icons[(player or { name = "__generic" }).name]
+ bg_icon.image = client_icon or client_icons.__generic
+ client_background:emit_signal "widget::redraw_needed"
+
+ for i, child in ipairs(layout.children) do
+ ---@diagnostic disable-next-line:undefined-field
+ child:index_handler(i)
+ end
+end
+
+function M.next_player()
+ local players_length = #layout.children
+ if players_length == 0 then
+ return
+ end
+
+ if M.active_player_index + 1 > players_length then
+ M.active_player_index = 1
+ else
+ M.active_player_index = M.active_player_index + 1
+ end
+
+ update_player(playerctl:list()[M.active_player_index])
+end
+
+function M.previous_player()
+ local players_length = #layout.children
+ if players_length == 0 then
+ return
+ end
+
+ if M.active_player_index - 1 < 1 then
+ M.active_player_index = players_length
+ else
+ M.active_player_index = M.active_player_index - 1
+ end
+
+ update_player(playerctl:list()[M.active_player_index])
+end
+
+---@param diff_player Playerctl.data
+local function recalculate_active_player(diff_player, vanished)
+ if type(diff_player) ~= "table" then
+ return
+ end
+ if #layout.children == 0 then
+ M.active_player_index = 1
+ update_player()
+ return
+ end
+
+ local active_player = players[M.active_player_index]
+ if not active_player then -- we're recovering from a state with no players
+ update_player(diff_player)
+ return
+ end
+
+ if diff_player.instance == active_player.instance and vanished then -- active player vanished; fall back to previous player
+ M.previous_player()
+ else -- non-active player appeared/vanished; try to find active player
+ for i, p in ipairs(playerctl:list()) do
+ if p.instance == active_player.instance then
+ M.active_player_index = i
+ update_player(p)
+ return
+ end
+ end
+
+ gdebug.print_warning(
+ "failed to find active player:\n " .. gdebug.dump_return(active_player, nil, 2):gsub("\n", "\n ")
+ )
+ M.active_player_index = 1
+ update_player(playerctl:list()[M.active_player_index])
+ end
+end
+
+local function register_player(player)
+ local widget = wibox.widget {
+ widget = wibox.container.constraint,
+ width = qui.PADDING,
+ strategy = "min",
+ {
+ widget = wibox.container.background,
+ shape = qui.shape,
+ bg = qcolor.palette.bg.lowest,
+ },
+ index_handler = function(self, index)
+ if M.active_player_index == index then
+ self.widget.bg = qcolor.palette.yellow()
+ else
+ self.widget.bg = qcolor.palette.bg.lowest
+ end
+ end,
+ }
+
+ ---@diagnostic disable-next-line:undefined-field
+ layout:add(widget)
+
+ recalculate_active_player(player, false)
+ mirror_player_list()
+end
+
+local function unregister_player(player)
+ ---@diagnostic disable-next-line:undefined-field
+ layout:remove(#layout.children)
+ recalculate_active_player(player, true)
+ mirror_player_list()
+end
+
+for _, player in ipairs(playerctl:list()) do
+ register_player(player)
+end
+
+-- recover state
+---@diagnostic disable-next-line:undefined-field
+local last_active_player = playerctl:list()[M.active_player_index]
+
+update_player(last_active_player)
+
+playerctl:connect_signal("player::appeared", function(_, player)
+ register_player(player)
+end)
+
+playerctl:connect_signal("player::vanished", function(_, player)
+ unregister_player(player)
+end)
+
+return M
diff --git a/.config/awesome/ui/statusbar/panel/widgets/music.lua b/.config/awesome/ui/statusbar/panel/widgets/music.old.lua
index eea7335..5631f66 100644
--- a/.config/awesome/ui/statusbar/panel/widgets/music.lua
+++ b/.config/awesome/ui/statusbar/panel/widgets/music.old.lua
@@ -1,3 +1,4 @@
+-- TODO: update to the new color format
local qnative = require "quarrel.native"
local qui = require "quarrel.ui"
local qvars = require "quarrel.vars"
@@ -12,7 +13,7 @@ local w_title = wibox.widget {
local w_artist = wibox.widget {
widget = wibox.container.background,
- fg = qvars.colors.dim.fg,
+ fg = qcolor.palette.dim.fg,
{
widget = wibox.widget.textbox,
text = "",
@@ -23,35 +24,35 @@ local w_progress_bar = wibox.widget {
widget = wibox.widget.progressbar,
max_value = 0,
value = 0,
- forced_height = qvars.char_height / 2,
+ forced_height = qui.CHAR_HEIGHT / 2,
forced_width = qvars.expanded_bar_size
- - (qvars.big_padding + qvars.big_padding * 2 + qvars.padding * 2)
- - (qvars.char_height / 1.25 + qvars.padding) * 3,
- color = qvars.colors.yellow,
- background_color = qvars.colors.black,
- shape = qvars.shape,
+ - (qui.BIG_PADDING + qui.BIG_PADDING * 2 + qui.PADDING * 2)
+ - (qui.CHAR_HEIGHT / 1.25 + qui.PADDING) * 3,
+ color = qcolor.palette.yellow,
+ background_color = qcolor.palette.black,
+ shape = qui.shape,
}
local music = wibox.widget(qui.styled {
widget = wibox.container.background,
{
widget = wibox.container.margin,
- margins = qvars.big_padding,
+ margins = qui.BIG_PADDING,
{
{
{
widget = wibox.container.background,
- bg = qvars.colors.bg,
- shape = qvars.shape,
+ bg = qcolor.palette.bg,
+ shape = qui.shape,
{
widget = wibox.container.margin,
- margins = qvars.padding,
+ margins = qui.PADDING,
{
{
widget = wibox.container.constraint,
width = qvars.expanded_bar_size
- - (qvars.big_padding + qvars.big_padding * 2 + qvars.padding * 2),
- height = qvars.char_height,
+ - (qui.BIG_PADDING + qui.BIG_PADDING * 2 + qui.PADDING * 2),
+ height = qui.CHAR_HEIGHT,
{
widget = wibox.container.scroll.horizontal,
speed = 50,
@@ -62,8 +63,8 @@ local music = wibox.widget(qui.styled {
{
widget = wibox.container.constraint,
width = qvars.expanded_bar_size
- - (qvars.big_padding + qvars.big_padding * 2 + qvars.padding * 2),
- height = qvars.char_height,
+ - (qui.BIG_PADDING + qui.BIG_PADDING * 2 + qui.PADDING * 2),
+ height = qui.CHAR_HEIGHT,
{
widget = wibox.container.scroll.horizontal,
speed = 50,
@@ -82,8 +83,8 @@ local music = wibox.widget(qui.styled {
nil,
{
widget = wibox.container.background,
- bg = qvars.colors.bg,
- shape = qvars.shape,
+ bg = qcolor.palette.bg,
+ shape = qui.shape,
w_progress_bar,
},
layout = wibox.layout.align.vertical,
diff --git a/.config/awesome/ui/statusbar/panel/widgets/power_menu.lua b/.config/awesome/ui/statusbar/panel/widgets/power_menu.lua
index feea829..01171df 100644
--- a/.config/awesome/ui/statusbar/panel/widgets/power_menu.lua
+++ b/.config/awesome/ui/statusbar/panel/widgets/power_menu.lua
@@ -1,19 +1,17 @@
local q = require "quarrel"
local qbind = require "quarrel.bind"
local qui = require "quarrel.ui"
-local qvars = require "quarrel.vars"
local wibox = require "wibox"
return wibox.widget {
qui.styled {
widget = wibox.container.background,
- bg = qvars.colors.black,
{
widget = wibox.widget.textbox,
text = "1",
buttons = {
- qbind:new {
- triggers = qvars.btns.left,
+ qbind {
+ triggers = qbind.btns.left,
press = function()
q.debug "from 1"
end,
@@ -24,13 +22,12 @@ return wibox.widget {
},
qui.styled {
widget = wibox.container.background,
- bg = qvars.colors.black,
{
widget = wibox.widget.textbox,
text = "2",
buttons = {
- qbind:new {
- triggers = qvars.btns.left,
+ qbind {
+ triggers = qbind.btns.left,
press = function()
q.debug "from 2"
end,
@@ -41,13 +38,12 @@ return wibox.widget {
},
qui.styled {
widget = wibox.container.background,
- bg = qvars.colors.black,
{
widget = wibox.widget.textbox,
text = "3",
buttons = {
- qbind:new {
- triggers = qvars.btns.left,
+ qbind {
+ triggers = qbind.btns.left,
press = function()
q.debug "from 3"
end,
diff --git a/.config/awesome/ui/statusbar/panel/widgets/weather.lua b/.config/awesome/ui/statusbar/panel/widgets/weather.lua
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/.config/awesome/ui/statusbar/panel/widgets/weather.lua
diff --git a/.config/awesome/ui/statusbar/panel/widgets/wifi.lua b/.config/awesome/ui/statusbar/panel/widgets/wifi.lua
index ad2234f..8ccff95 100644
--- a/.config/awesome/ui/statusbar/panel/widgets/wifi.lua
+++ b/.config/awesome/ui/statusbar/panel/widgets/wifi.lua
@@ -1,27 +1,30 @@
local lgi = require "lgi"
local phosphor = require "assets.phosphor"
+local qcolor = require "quarrel.color"
local qui = require "quarrel.ui"
-local qvars = require "quarrel.vars"
local wibox = require "wibox"
local glib = lgi.GLib
+local wifi_icon = qui.icon {
+ icon = phosphor.wifi_x_fill,
+ color = qcolor.palette.red(),
+ widget = {
+ id = "icon",
+ },
+}
+
local wifi = wibox.widget(qui.styled {
widget = wibox.container.background,
+ bg = qcolor.palette.bg.high,
{
widget = wibox.container.margin,
- margins = qvars.big_padding,
+ margins = qui.BIG_PADDING,
{
{
widget = wibox.container.place,
valign = "center",
halign = "center",
- qui.icon {
- icon = phosphor.wifi_x_fill,
- color = qvars.colors.red,
- widget = {
- id = "icon",
- },
- },
+ wifi_icon,
},
{
widget = wibox.widget.textbox,
@@ -29,7 +32,7 @@ local wifi = wibox.widget(qui.styled {
id = "essid",
},
layout = wibox.layout.fixed.horizontal,
- spacing = qvars.padding,
+ spacing = qui.PADDING,
},
},
})
@@ -43,8 +46,8 @@ awesome.connect_signal("services::wifi", function(essid, _, connected)
end)
awesome.connect_signal("services::wifi::icon", function(icon, color)
- wifi:get_children_by_id("icon")[1].image = icon
- wifi:get_children_by_id("icon")[1].stylesheet = qui.recolor(color)
+ wifi_icon.image = icon
+ wifi_icon.stylesheet = qui.recolor(color)
end)
return wifi
diff --git a/.config/awesome/ui/statusbar/widgets/displays.lua b/.config/awesome/ui/statusbar/widgets/displays.lua
index 62e4398..96cf84c 100644
--- a/.config/awesome/ui/statusbar/widgets/displays.lua
+++ b/.config/awesome/ui/statusbar/widgets/displays.lua
@@ -1,7 +1,7 @@
local battery = require "services.battery"
local phosphor = require "assets.phosphor"
+local qcolor = require "quarrel.color"
local qui = require "quarrel.ui"
-local qvars = require "quarrel.vars"
local wibox = require "wibox"
local function create_display(icon, color)
@@ -16,28 +16,32 @@ local function create_display(icon, color)
}
end
-local d_battery = create_display(phosphor.battery_vertical_warning_fill, qvars.colors.red)
+local d_battery = create_display(phosphor.battery_vertical_warning_fill, qcolor.palette.red())
battery:connect_signal("icon", function(_, icon, color)
d_battery.widget.image = icon
d_battery.widget.stylesheet = qui.recolor(color)
end)
-local d_brightness = create_display(phosphor.moon_fill, qvars.colors.fg)
-awesome.connect_signal("services::brightness::icon", function(icon, color)
- d_brightness.widget.image = icon
- d_brightness.widget.stylesheet = qui.recolor(color)
-end)
+-- local d_brightness = create_display(phosphor.moon_fill, qcolor.palette.fg())
+-- awesome.connect_signal("services::brightness::icon", function(icon, color)
+-- d_brightness.widget.image = icon
+-- d_brightness.widget.stylesheet = qui.recolor(color)
+-- end)
-local d_audio = create_display(phosphor.speaker_simple_slash_fill, qvars.colors.red)
+local d_audio = create_display(phosphor.speaker_simple_slash_fill, qcolor.palette.red())
awesome.connect_signal("services::audio::icon", function(icon, color)
d_audio.widget.image = icon
d_audio.widget.stylesheet = qui.recolor(color)
end)
-local d_wifi = create_display(phosphor.wifi_x_fill, qvars.colors.red)
+local d_wifi = create_display(phosphor.wifi_x_fill, qcolor.palette.red())
awesome.connect_signal("services::wifi::icon", function(icon, color)
d_wifi.widget.image = icon
d_wifi.widget.stylesheet = qui.recolor(color)
end)
-return { audio = d_audio, battery = d_battery, brightness = d_brightness, wifi = d_wifi }
+return {
+ audio = d_audio,
+ battery = d_battery, --[[brightness = d_brightness,]]
+ wifi = d_wifi,
+}
diff --git a/.config/awesome/ui/statusbar/widgets/taglist.lua b/.config/awesome/ui/statusbar/widgets/taglist.lua
index df114df..3b81173 100644
--- a/.config/awesome/ui/statusbar/widgets/taglist.lua
+++ b/.config/awesome/ui/statusbar/widgets/taglist.lua
@@ -10,7 +10,7 @@ return function(s)
screen = s,
filter = awful.widget.taglist.filter.all,
layout = {
- spacing = qvars.padding,
+ spacing = qui.PADDING,
layout = wibox.layout.fixed.vertical,
},
widget_template = {
@@ -21,8 +21,8 @@ return function(s)
self.widget = qui.icon {
icon = phosphor[next(tag:clients()) and "circle_fill" or "circle_bold"],
widget = {
- forced_height = qvars.char_height / 1.5,
- forced_width = qvars.char_height / 1.5,
+ forced_height = qui.CHAR_HEIGHT / 1.5,
+ forced_width = qui.CHAR_HEIGHT / 1.5,
},
}
-- self.widget = wibox.widget {
@@ -33,44 +33,44 @@ return function(s)
if tag.selected then
return
end
- self.widget.stylesheet = qui.recolor(qvars.colors.yellow)
+ self.widget.stylesheet = qui.recolor(qcolor.palette.yellow())
end)
self:connect_signal("mouse::leave", function()
if tag.selected then
return
end
- self.widget.stylesheet = qui.recolor(qvars.colors.fg)
+ self.widget.stylesheet = qui.recolor(qcolor.palette.fg())
end)
if tag.selected then
- self.widget.stylesheet = qui.recolor(qvars.colors.yellow)
+ self.widget.stylesheet = qui.recolor(qcolor.palette.yellow())
return
end
- self.widget.stylesheet = qui.recolor(qvars.colors.fg)
+ self.widget.stylesheet = qui.recolor(qcolor.palette.fg())
end,
update_callback = function(self, tag)
self.widget.image = phosphor[next(tag:clients()) and "circle_fill" or "circle_bold"]
if tag.selected then
- self.widget.stylesheet = qui.recolor(qvars.colors.yellow)
+ self.widget.stylesheet = qui.recolor(qcolor.palette.yellow())
else
- self.widget.stylesheet = qui.recolor(qvars.colors.fg)
+ self.widget.stylesheet = qui.recolor(qcolor.palette.fg())
end
end,
},
buttons = {
- qbind:new {
- triggers = qvars.btns.left,
+ qbind {
+ triggers = qbind.btns.left,
press = function(t)
t:view_only()
end,
hidden = true,
},
- qbind:new {
- mods = qvars.mods.M,
- triggers = qvars.btns.left,
+ qbind {
+ mods = qbind.mods.M,
+ triggers = qbind.btns.left,
press = function(t)
if client.focus then
client.focus:move_to_tag(t)
@@ -78,8 +78,8 @@ return function(s)
end,
hidden = true,
},
- qbind:new {
- triggers = qvars.btns.right,
+ qbind {
+ triggers = qbind.btns.right,
press = awful.tag.viewtoggle,
hidden = true,
},
diff --git a/.config/awesome/ui/statusbar/widgets/taglist_new.lua b/.config/awesome/ui/statusbar/widgets/taglist_new.lua
new file mode 100644
index 0000000..79b8a96
--- /dev/null
+++ b/.config/awesome/ui/statusbar/widgets/taglist_new.lua
@@ -0,0 +1,147 @@
+local awful = require "awful"
+local qbind = require "quarrel.bind"
+local qcolor = require "quarrel.color"
+local qui = require "quarrel.ui"
+local qvars = require "quarrel.vars"
+local wibox = require "wibox"
+
+local empty_indicator = wibox.widget {
+ widget = wibox.container.place,
+ {
+ widget = wibox.container.constraint,
+ strategy = "exact",
+ height = qui.PADDING,
+ width = qui.PADDING,
+ {
+ widget = wibox.container.background,
+ bg = qcolor.palette.border.variant,
+ shape = qui.shape,
+ },
+ },
+}
+
+local client_indicator = wibox.widget {
+ widget = wibox.container.place,
+ {
+ widget = wibox.container.constraint,
+ height = qui.CHAR_HEIGHT,
+ width = qui.PADDING,
+ strategy = "exact",
+ {
+ widget = wibox.container.background,
+ shape = qui.shape,
+ bg = qcolor.palette.fg(),
+ },
+ },
+}
+
+local function update_tasklist(layout, tag, indicated_empty)
+ local client_delta = #tag:clients() - #layout.children
+ if client_delta < 0 then
+ for _ = 1, -(client_delta + (indicated_empty and 1 or 0)) do
+ layout:remove(1)
+ end
+ elseif client_delta > 0 then
+ for _ = 1, client_delta do
+ layout:add(client_indicator)
+ end
+ end
+end
+
+return function(s)
+ return awful.widget.taglist {
+ screen = s,
+ filter = awful.widget.taglist.filter.all,
+ layout = {
+ spacing = qui.PADDING,
+ layout = wibox.layout.fixed.vertical,
+ },
+ widget_template = {
+ widget = wibox.container.constraint,
+ strategy = "min",
+ height = qui.CHAR_HEIGHT,
+ qui.styled {
+ widget = wibox.container.background,
+ bg = qcolor.palette.bg.high,
+ {
+ widget = wibox.container.margin,
+ margins = qui.PADDING,
+ {
+ layout = wibox.layout.flex.vertical,
+ spacing = qui.PADDING,
+ },
+ },
+ },
+ create_callback = function(
+ self,
+ tag --[[@as tag]]
+ )
+ if tag.selected then
+ self.widget.border_color = qcolor.palette.yellow()
+ end
+
+ if #tag:clients() ~= 0 then
+ self.widget.widget.spacing = qui.PADDING
+ if self.indicated_empty then
+ self.widget.widget.widget:remove(1)
+ self.indicated_empty = false
+ end
+ else
+ if not self.indicated_empty then
+ self.widget.widget.widget:add(empty_indicator)
+ self.indicated_empty = true
+ end
+ end
+
+ update_tasklist(self.widget.widget.widget, tag, self.indicated_empty)
+ end,
+ update_callback = function(self, tag)
+ if tag.selected then
+ self.widget.border_color = qcolor.palette.yellow()
+ else
+ self.widget.border_color = qcolor.palette.border()
+ end
+
+ if #tag:clients() ~= 0 then
+ self.widget.widget.spacing = qui.PADDING
+ if self.indicated_empty then
+ self.widget.widget.widget:remove(1)
+ self.indicated_empty = false
+ end
+ else
+ self.widget.widget.spacing = nil
+ if not self.indicated_empty then
+ self.widget.widget.widget:add(empty_indicator)
+ self.indicated_empty = true
+ end
+ end
+
+ update_tasklist(self.widget.widget.widget, tag, self.indicated_empty)
+ end,
+ },
+ buttons = {
+ qbind {
+ triggers = qbind.btns.left,
+ press = function(t)
+ t:view_only()
+ end,
+ hidden = true,
+ },
+ qbind {
+ mods = qbind.mods.M,
+ triggers = qbind.btns.left,
+ press = function(t)
+ if client.focus then
+ client.focus:move_to_tag(t)
+ end
+ end,
+ hidden = true,
+ },
+ qbind {
+ triggers = qbind.btns.right,
+ press = awful.tag.viewtoggle,
+ hidden = true,
+ },
+ },
+ }
+end
diff --git a/.config/awesome/ui/statusbar/widgets/tasklist.lua b/.config/awesome/ui/statusbar/widgets/tasklist.lua
deleted file mode 100644
index 9656185..0000000
--- a/.config/awesome/ui/statusbar/widgets/tasklist.lua
+++ /dev/null
@@ -1,30 +0,0 @@
-local awful = require "awful"
-local gears = require "gears"
-local qvars = require "quarrel.vars"
-local wibox = require "wibox"
-
-return function(s)
- return awful.widget.tasklist {
- screen = s,
- filter = awful.widget.tasklist.filter.currenttags,
- -- buttons = tasklist_buttons,
- layout = {
- spacing = qvars.padding,
- layout = wibox.layout.flex.vertical,
- },
- widget_template = {
- widget = wibox.container.place,
- valign = "center",
- halign = "center",
- -- {
- -- widget = awful.widget.clienticon,
-
- -- }
- {
- widget = awful.widget.clienticon,
- forced_width = qvars.char_height,
- forced_height = qvars.char_height,
- },
- },
- }
-end
diff --git a/.config/awesome/ui/wicked/consts.lua b/.config/awesome/ui/wicked/consts.lua
new file mode 100644
index 0000000..e00cae5
--- /dev/null
+++ b/.config/awesome/ui/wicked/consts.lua
@@ -0,0 +1,5 @@
+local C = {}
+
+C.NOTIF_TIMEOUT = 3
+
+return C
diff --git a/.config/awesome/ui/wicked/init.lua b/.config/awesome/ui/wicked/init.lua
index ddbf912..dea28d5 100644
--- a/.config/awesome/ui/wicked/init.lua
+++ b/.config/awesome/ui/wicked/init.lua
@@ -2,19 +2,22 @@ local awful = require "awful"
local beautiful = require "beautiful"
local gshape = require "gears.shape"
local naughty = require "naughty"
+local qanim = require "quarrel.animation"
local qui = require "quarrel.ui"
local qvars = require "quarrel.vars"
local wibox = require "wibox"
local rtimed = require("lib.rubato").timed
local easing = require("lib.rubato").easing
local gtimer = require "gears.timer"
+local qcolor = require "quarrel.color"
local qmarkup = require "quarrel.markup"
-return function(n)
- local intertext_margin = (n.title ~= "" or n.message ~= "") and qvars.padding or 0
- local title_height = n.title ~= "" and qvars.char_height or 0
- local message_height = n.message ~= "" and qvars.char_height or 0
- -- local app_name_height = n.app_name ~= "" and
+local M = require "ui.wicked.consts"
+
+function M.new(n)
+ local intertext_margin = (n.title ~= "" or n.message ~= "") and qui.PADDING or 0
+ local title_height = n.title ~= "" and qui.CHAR_HEIGHT or 0
+ local message_height = n.message ~= "" and qui.CHAR_HEIGHT or 0
local app_name
if n.app_name == "" then
app_name = n._private._foreign and "Unknown" or "Awesome"
@@ -29,22 +32,19 @@ return function(n)
margins = beautiful.useless_gap * 2,
})
end,
- bg = qvars.colors.transparent,
+ bg = qcolor.palette.transparent,
border_width = 0,
shape = gshape.rectangle,
widget_template = {
widget = wibox.container.constraint,
- height = qvars.big_padding * 2
- + qvars.char_height
- + qvars.border_width
- + qvars.big_padding * 2
+ height = qui.BIG_PADDING * 2
+ + qui.CHAR_HEIGHT
+ + qui.BORDER_WIDTH
+ + qui.BIG_PADDING * 2
-- + title_height
-- + message_height
-- + intertext_margin,
- + (
- n.icon and qvars.char_height * 2 + qvars.padding
- or (title_height + message_height + intertext_margin)
- ),
+ + (n.icon and qui.CHAR_HEIGHT * 2 + qui.PADDING or (title_height + message_height + intertext_margin)),
strategy = "exact",
{
@@ -59,7 +59,7 @@ return function(n)
{
{
widget = wibox.container.margin,
- margins = qvars.big_padding,
+ margins = qui.BIG_PADDING,
{
{
widget = wibox.widget.textbox,
@@ -69,12 +69,12 @@ return function(n)
{
widget = wibox.container.radialprogressbar,
- max_value = qvars.notif_timeout,
- border_color = qvars.colors.black,
- color = qvars.colors.yellow,
- border_width = qvars.border_width,
- forced_height = qvars.char_height,
- forced_width = qvars.char_height,
+ max_value = M.NOTIF_TIMEOUT,
+ border_color = qcolor.palette.bg.lowest,
+ color = qcolor.palette.yellow(),
+ border_width = qui.BORDER_WIDTH,
+ forced_height = qui.CHAR_HEIGHT,
+ forced_width = qui.CHAR_HEIGHT,
id = "progress",
},
layout = wibox.layout.align.horizontal,
@@ -82,20 +82,20 @@ return function(n)
},
{
widget = wibox.container.constraint,
- height = qvars.border_width,
+ height = qui.BORDER_WIDTH,
width = beautiful.notification_max_width,
strategy = "exact",
{
widget = wibox.container.background,
- bg = qvars.colors.bright.black,
+ bg = qcolor.palette.border(),
},
},
{
widget = wibox.container.margin,
- margins = qvars.big_padding,
+ margins = qui.BIG_PADDING,
{
widget = wibox.container.constraint,
- height = n.icon and qvars.char_height * 2 + qvars.padding
+ height = n.icon and qui.CHAR_HEIGHT * 2 + qui.PADDING
or (title_height + message_height + intertext_margin),
strategy = "exact",
{
@@ -103,7 +103,7 @@ return function(n)
widget = wibox.container.background,
{
widget = naughty.widget.icon,
- shape = qvars.shape,
+ shape = qui.shape,
notification = n,
},
},
@@ -130,7 +130,7 @@ return function(n)
layout = wibox.layout.fixed.vertical,
},
fill_space = true,
- spacing = n.icon and qvars.big_padding or nil,
+ spacing = n.icon and qui.BIG_PADDING or nil,
layout = wibox.layout.fixed.horizontal,
},
},
@@ -158,12 +158,10 @@ return function(n)
end,
}
- local position = rtimed {
- duration = qvars.anim_duration * 2,
- intro = qvars.anim_intro * 2,
+ local position = qanim:new {
+ duration = qvars.anim_duration,
pos = 0,
- easing = easing.quadratic,
- clamp_position = true,
+ easing = qvars.easing,
subscribed = function(pos)
gtimer.delayed_call(function()
notif.widget.widget:move(1, function(geo, args)
@@ -179,8 +177,8 @@ return function(n)
end,
}
local opacity = rtimed {
- duration = qvars.anim_duration * 2,
- intro = qvars.anim_intro * 2,
+ duration = qvars.anim_duration,
+ intro = qvars.anim_intro,
easing = easing.quadratic,
pos = 0,
clamp_position = true,
@@ -192,12 +190,20 @@ return function(n)
n:disconnect_signal("destroyed", notif._private.destroy_callback)
function notif._private.destroy_callback()
opacity.target = 0
- position.target = 0
+ position:set(0)
hiding = true
end
n:weak_connect_signal("destroyed", notif._private.destroy_callback)
+ --- for some reason when urgency is critical, it somehow makes it not disappear
+ --- why? dunno, FIXME
opacity.target = 1
- position.target = qvars.char_width * 48
- progress.target = qvars.notif_timeout
+ position:set(beautiful.notification_max_width)
+ progress.target = M.NOTIF_TIMEOUT
end
+
+return setmetatable(M, {
+ __call = function(_, ...)
+ return M.new(...)
+ end,
+})