diff options
author | delta <darkussdelta@gmail.com> | 2025-07-04 00:38:29 +0200 |
---|---|---|
committer | delta <darkussdelta@gmail.com> | 2025-07-04 00:38:29 +0200 |
commit | b3530d7c4a102935fa26498a160ee1dc6c1e9c03 (patch) | |
tree | d7751206a694bc5de2d6b34b0c077cfcd1855798 | |
parent | df75ec5ed5e3848c497f0439acb43ec9246ad3e7 (diff) |
:3
133 files changed, 7691 insertions, 2827 deletions
diff --git a/.Xresources b/.Xresources index bfe837f..ce34628 100644 --- a/.Xresources +++ b/.Xresources @@ -1,6 +1,8 @@ Xft.dpi: 120 +/* Xft.dpi: 90 */ Xft.rgba: rgb -Xcursor.theme: phinger-cursors +Xcursor.theme: phinger-cursors-dark +Xcursor.size: 24 /* ! special */ /* *.foreground: #dfe2e7 */ diff --git a/.assets/README.md b/.assets/README.md new file mode 100644 index 0000000..c050166 --- /dev/null +++ b/.assets/README.md @@ -0,0 +1,4 @@ + + +# Credits +- [Vixima](https://github.com/Vixima): Various UI sounds diff --git a/.assets/preview.png b/.assets/preview.png Binary files differnew file mode 100644 index 0000000..c1516fc --- /dev/null +++ b/.assets/preview.png 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 <albel727@ngs.ru> --- @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 Binary files differdeleted file mode 100644 index 5ff33fd..0000000 --- a/.config/awesome/quarrel/lua-rust.tar.gz +++ /dev/null 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, +}) diff --git a/.config/fish/bindings.fish b/.config/fish/bindings.fish index 1ed7313..1a371e1 100644 --- a/.config/fish/bindings.fish +++ b/.config/fish/bindings.fish @@ -1 +1,3 @@ bind \b backward-kill-word +bind \eq fish_commandline_toggle +bind alt-s "fish_commandline_prepend doas" diff --git a/.config/fish/colors.fish b/.config/fish/colors.fish index de32d6d..381b0e9 100644 --- a/.config/fish/colors.fish +++ b/.config/fish/colors.fish @@ -9,15 +9,15 @@ set fish_color_error white set fish_color_param white set fish_color_valid_path white set fish_color_option white -set fish_color_comment "#8893a5" +set fish_color_comment "#77828c" set fish_color_operator white set fish_color_escape yellow -set fish_color_autosuggestion "#8893a5" +set fish_color_autosuggestion "#77828c" set fish_color_search_match --background=black # pager -set fish_pager_color_progress "#8893a5" +set fish_pager_color_progress "#77828c" set fish_pager_color_prefix white -set fish_pager_color_completion "#8893a5" -set fish_pager_color_description "#8893a5" +set fish_pager_color_completion "#77828c" +set fish_pager_color_description "#77828c" set fish_pager_color_selected_background --background=black diff --git a/.config/fish/completions/beet.fish b/.config/fish/completions/beet.fish new file mode 100644 index 0000000..8cc38e0 --- /dev/null +++ b/.config/fish/completions/beet.fish @@ -0,0 +1,523 @@ + +function __fish_beet_needs_command + set cmd (commandline -opc) + if test (count $cmd) -eq 1 + return 0 + end + return 1 +end + +function __fish_beet_using_command + set cmd (commandline -opc) + set needle (count $cmd) + if test $needle -gt 1 + if begin test $argv[1] = $cmd[2]; + and not contains -- $cmd[$needle] $FIELDS; end + return 0 + end + end + return 1 +end + +function __fish_beet_use_extra + set cmd (commandline -opc) + set needle (count $cmd) + if test $argv[2] = $cmd[$needle] + return 0 + end + return 1 +end + +set CMDS bpmanalyser clearart completion config convert dup duplicates embedart extractart fetchart fields fish ? help imp im import info ls list miss missing mod modify mv move rm remove spotify stats upd up update version write + +set FIELDS acoustid_fingerprint: acoustid_id: added: album: album_id: albumartist: albumartist_credit: albumartist_sort: albumdisambig: albumstatus: albumtotal: albumtype: albumtypes: arranger: artist: artist_credit: artist_sort: artpath: asin: bitdepth: bitrate: bpm: catalognum: channels: comments: comp: composer: composer_sort: country: day: disc: discogs_albumid: discogs_artistid: discogs_labelid: disctitle: disctotal: encoder: filesize: format: genre: grouping: id: initial_key: isrc: label: language: length: lyricist: lyrics: mb_albumartistid: mb_albumid: mb_artistid: mb_releasegroupid: mb_releasetrackid: mb_trackid: mb_workid: media: missing: month: mtime: original_day: original_month: original_year: path: r128_album_gain: r128_track_gain: releasegroupdisambig: rg_album_gain: rg_album_peak: rg_track_gain: rg_track_peak: samplerate: script: singleton: style: title: track: trackdisambig: tracktotal: work: work_disambig: year: + + +# ====== setup basic beet completion ===== + +complete -c beet -n '__fish_beet_needs_command' -l format-item -f -d 'print with custom format' +complete -c beet -n '__fish_beet_needs_command' -l format-album -f -d 'print with custom format' +complete -c beet -n '__fish_beet_needs_command' -s l -l library -f -r -d 'library database file to use' +complete -c beet -n '__fish_beet_needs_command' -s d -l directory -f -r -d 'destination music directory' +complete -c beet -n '__fish_beet_needs_command' -s v -l verbose -f -d 'print debugging information' +complete -c beet -n '__fish_beet_needs_command' -s c -l config -f -r -d 'path to configuration file' +complete -c beet -n '__fish_beet_needs_command' -s h -l help -f -d 'print this help message and exit' + +# ====== setup field completion for subcommands ===== + +# ------ fieldsetups for bpmanalyser ------- +complete -c beet -n '__fish_beet_needs_command' -a bpmanalyser -f -d 'analyse your songs for tempo and write it into the bpm tag' +complete -c beet -n '__fish_beet_using_command bpmanalyser' -a '$FIELDS' -f -d 'fieldname' + +# ------ fieldsetups for clearart ------- +complete -c beet -n '__fish_beet_needs_command' -a clearart -f -d 'remove images from file metadata' +complete -c beet -n '__fish_beet_using_command clearart' -a '$FIELDS' -f -d 'fieldname' + +# ------ fieldsetups for completion ------- +complete -c beet -n '__fish_beet_needs_command' -a completion -f -d 'print shell script that provides command line completion' +complete -c beet -n '__fish_beet_using_command completion' -a '$FIELDS' -f -d 'fieldname' + +# ------ fieldsetups for config ------- +complete -c beet -n '__fish_beet_needs_command' -a config -f -d 'show or edit the user configuration' +complete -c beet -n '__fish_beet_using_command config' -a '$FIELDS' -f -d 'fieldname' + +# ------ fieldsetups for convert ------- +complete -c beet -n '__fish_beet_needs_command' -a convert -f -d 'convert to external location' +complete -c beet -n '__fish_beet_using_command convert' -a '$FIELDS' -f -d 'fieldname' + +# ------ fieldsetups for dup ------- +complete -c beet -n '__fish_beet_needs_command' -a dup -f -d 'List duplicate tracks or albums.' +complete -c beet -n '__fish_beet_using_command dup' -a '$FIELDS' -f -d 'fieldname' + +# ------ fieldsetups for duplicates ------- +complete -c beet -n '__fish_beet_needs_command' -a duplicates -f -d 'List duplicate tracks or albums.' +complete -c beet -n '__fish_beet_using_command duplicates' -a '$FIELDS' -f -d 'fieldname' + +# ------ fieldsetups for embedart ------- +complete -c beet -n '__fish_beet_needs_command' -a embedart -f -d 'embed image files into file metadata' +complete -c beet -n '__fish_beet_using_command embedart' -a '$FIELDS' -f -d 'fieldname' + +# ------ fieldsetups for extractart ------- +complete -c beet -n '__fish_beet_needs_command' -a extractart -f -d 'extract an image from file metadata' +complete -c beet -n '__fish_beet_using_command extractart' -a '$FIELDS' -f -d 'fieldname' + +# ------ fieldsetups for fetchart ------- +complete -c beet -n '__fish_beet_needs_command' -a fetchart -f -d 'download album art' +complete -c beet -n '__fish_beet_using_command fetchart' -a '$FIELDS' -f -d 'fieldname' + +# ------ fieldsetups for fields ------- +complete -c beet -n '__fish_beet_needs_command' -a fields -f -d 'show fields available for queries and format strings' +complete -c beet -n '__fish_beet_using_command fields' -a '$FIELDS' -f -d 'fieldname' + +# ------ fieldsetups for fish ------- +complete -c beet -n '__fish_beet_needs_command' -a fish -f -d 'generate Fish shell tab completions' +complete -c beet -n '__fish_beet_using_command fish' -a '$FIELDS' -f -d 'fieldname' + +# ------ fieldsetups for \? ------- +complete -c beet -n '__fish_beet_needs_command' -a \? -f -d 'give detailed help on a specific sub-command' +complete -c beet -n '__fish_beet_using_command \?' -a '$FIELDS' -f -d 'fieldname' + +# ------ fieldsetups for help ------- +complete -c beet -n '__fish_beet_needs_command' -a help -f -d 'give detailed help on a specific sub-command' +complete -c beet -n '__fish_beet_using_command help' -a '$FIELDS' -f -d 'fieldname' + +# ------ fieldsetups for imp ------- +complete -c beet -n '__fish_beet_needs_command' -a imp -f -d 'import new music' +complete -c beet -n '__fish_beet_using_command imp' -a '$FIELDS' -f -d 'fieldname' + +# ------ fieldsetups for im ------- +complete -c beet -n '__fish_beet_needs_command' -a im -f -d 'import new music' +complete -c beet -n '__fish_beet_using_command im' -a '$FIELDS' -f -d 'fieldname' + +# ------ fieldsetups for import ------- +complete -c beet -n '__fish_beet_needs_command' -a import -f -d 'import new music' +complete -c beet -n '__fish_beet_using_command import' -a '$FIELDS' -f -d 'fieldname' + +# ------ fieldsetups for info ------- +complete -c beet -n '__fish_beet_needs_command' -a info -f -d 'show file metadata' +complete -c beet -n '__fish_beet_using_command info' -a '$FIELDS' -f -d 'fieldname' + +# ------ fieldsetups for ls ------- +complete -c beet -n '__fish_beet_needs_command' -a ls -f -d 'query the library' +complete -c beet -n '__fish_beet_using_command ls' -a '$FIELDS' -f -d 'fieldname' + +# ------ fieldsetups for list ------- +complete -c beet -n '__fish_beet_needs_command' -a list -f -d 'query the library' +complete -c beet -n '__fish_beet_using_command list' -a '$FIELDS' -f -d 'fieldname' + +# ------ fieldsetups for miss ------- +complete -c beet -n '__fish_beet_needs_command' -a miss -f -d 'List missing tracks.' +complete -c beet -n '__fish_beet_using_command miss' -a '$FIELDS' -f -d 'fieldname' + +# ------ fieldsetups for missing ------- +complete -c beet -n '__fish_beet_needs_command' -a missing -f -d 'List missing tracks.' +complete -c beet -n '__fish_beet_using_command missing' -a '$FIELDS' -f -d 'fieldname' + +# ------ fieldsetups for mod ------- +complete -c beet -n '__fish_beet_needs_command' -a mod -f -d 'change metadata fields' +complete -c beet -n '__fish_beet_using_command mod' -a '$FIELDS' -f -d 'fieldname' + +# ------ fieldsetups for modify ------- +complete -c beet -n '__fish_beet_needs_command' -a modify -f -d 'change metadata fields' +complete -c beet -n '__fish_beet_using_command modify' -a '$FIELDS' -f -d 'fieldname' + +# ------ fieldsetups for mv ------- +complete -c beet -n '__fish_beet_needs_command' -a mv -f -d 'move or copy items' +complete -c beet -n '__fish_beet_using_command mv' -a '$FIELDS' -f -d 'fieldname' + +# ------ fieldsetups for move ------- +complete -c beet -n '__fish_beet_needs_command' -a move -f -d 'move or copy items' +complete -c beet -n '__fish_beet_using_command move' -a '$FIELDS' -f -d 'fieldname' + +# ------ fieldsetups for rm ------- +complete -c beet -n '__fish_beet_needs_command' -a rm -f -d 'remove matching items from the library' +complete -c beet -n '__fish_beet_using_command rm' -a '$FIELDS' -f -d 'fieldname' + +# ------ fieldsetups for remove ------- +complete -c beet -n '__fish_beet_needs_command' -a remove -f -d 'remove matching items from the library' +complete -c beet -n '__fish_beet_using_command remove' -a '$FIELDS' -f -d 'fieldname' + +# ------ fieldsetups for spotify ------- +complete -c beet -n '__fish_beet_needs_command' -a spotify -f -d 'build a Spotify playlist' +complete -c beet -n '__fish_beet_using_command spotify' -a '$FIELDS' -f -d 'fieldname' + +# ------ fieldsetups for stats ------- +complete -c beet -n '__fish_beet_needs_command' -a stats -f -d 'show statistics about the library or a query' +complete -c beet -n '__fish_beet_using_command stats' -a '$FIELDS' -f -d 'fieldname' + +# ------ fieldsetups for upd ------- +complete -c beet -n '__fish_beet_needs_command' -a upd -f -d 'update the library' +complete -c beet -n '__fish_beet_using_command upd' -a '$FIELDS' -f -d 'fieldname' + +# ------ fieldsetups for up ------- +complete -c beet -n '__fish_beet_needs_command' -a up -f -d 'update the library' +complete -c beet -n '__fish_beet_using_command up' -a '$FIELDS' -f -d 'fieldname' + +# ------ fieldsetups for update ------- +complete -c beet -n '__fish_beet_needs_command' -a update -f -d 'update the library' +complete -c beet -n '__fish_beet_using_command update' -a '$FIELDS' -f -d 'fieldname' + +# ------ fieldsetups for version ------- +complete -c beet -n '__fish_beet_needs_command' -a version -f -d 'output version information' +complete -c beet -n '__fish_beet_using_command version' -a '$FIELDS' -f -d 'fieldname' + +# ------ fieldsetups for write ------- +complete -c beet -n '__fish_beet_needs_command' -a write -f -d 'write tag information to files' +complete -c beet -n '__fish_beet_using_command write' -a '$FIELDS' -f -d 'fieldname' + + + +# ====== completions for bpmanalyser ===== +complete -c beet -n '__fish_beet_using_command bpmanalyser' -s d -l dry-run -f -d '[default: False] display the bpm values but do not update the library items' +complete -c beet -n '__fish_beet_using_command bpmanalyser' -s w -l write -f -d '[default: True] write the bpm values to the media files' +complete -c beet -n '__fish_beet_using_command bpmanalyser' -r -s t -l threads -f -d '[default: 8] the number of threads to run in parallel' +complete -c beet -n '__fish_beet_using_command bpmanalyser' -s f -l force -f -d '[default: False] force analysis of items with non-zero bpm values' +complete -c beet -n '__fish_beet_using_command bpmanalyser' -s q -l quiet -f -d '[default: False] mute all output' +complete -c beet -n '__fish_beet_using_command bpmanalyser' -s v -l version -f -d 'show plugin version' +complete -c beet -n '__fish_beet_using_command bpmanalyser' -s h -l help -f -d 'print help' + + +# ====== completions for clearart ===== +complete -c beet -n '__fish_beet_using_command clearart' -s y -l yes -f -d 'skip confirmation' +complete -c beet -n '__fish_beet_using_command clearart' -s h -l help -f -d 'print help' + + +# ====== completions for completion ===== +complete -c beet -n '__fish_beet_using_command completion' -s h -l help -f -d 'print help' + + +# ====== completions for config ===== +complete -c beet -n '__fish_beet_using_command config' -s p -l paths -f -d 'show files that configuration was loaded from' +complete -c beet -n '__fish_beet_using_command config' -s e -l edit -f -d 'edit user configuration with $EDITOR' +complete -c beet -n '__fish_beet_using_command config' -s d -l defaults -f -d 'include the default configuration' +complete -c beet -n '__fish_beet_using_command config' -s c -l clear -f -d 'do not redact sensitive fields' +complete -c beet -n '__fish_beet_using_command config' -s h -l help -f -d 'print help' + + +# ====== completions for convert ===== +complete -c beet -n '__fish_beet_using_command convert' -s p -l pretend -f -d 'show actions but do nothing' +complete -c beet -n '__fish_beet_using_command convert' -r -s t -l threads -f -d 'change the number of threads, defaults to maximum available processors' +complete -c beet -n '__fish_beet_using_command convert' -s k -l keep-new -f -d 'keep only the converted and move the old files' +complete -c beet -n '__fish_beet_using_command convert' -r -s d -l dest -f -d 'set the destination directory' +complete -c beet -n '__fish_beet_using_command convert' -r -s f -l format -f -d 'set the target format of the tracks' +complete -c beet -n '__fish_beet_using_command convert' -s y -l yes -f -d 'do not ask for confirmation' +complete -c beet -n '__fish_beet_using_command convert' -s l -l link -f -d 'symlink files that do not need transcoding.' +complete -c beet -n '__fish_beet_using_command convert' -s H -l hardlink -f -d 'hardlink files that do not need transcoding. Overrides --link.' +complete -c beet -n '__fish_beet_using_command convert' -s a -l album -f -d 'match albums instead of tracks' +complete -c beet -n '__fish_beet_using_command convert' -s h -l help -f -d 'print help' + + +# ====== completions for dup ===== +complete -c beet -n '__fish_beet_using_command dup' -s c -l count -f -d 'show duplicate counts' +complete -c beet -n '__fish_beet_using_command dup' -r -s C -l checksum -f -d 'report duplicates based on arbitrary command' +complete -c beet -n '__fish_beet_using_command dup' -s d -l delete -f -d 'delete items from library and disk' +complete -c beet -n '__fish_beet_using_command dup' -s F -l full -f -d 'show all versions of duplicate tracks or albums' +complete -c beet -n '__fish_beet_using_command dup' -s s -l strict -f -d 'report duplicates only if all attributes are set' +complete -c beet -n '__fish_beet_using_command dup' -r -s k -l key -f -d 'report duplicates based on keys (use multiple times)' +complete -c beet -n '__fish_beet_using_command dup' -s M -l merge -f -d 'merge duplicate items' +complete -c beet -n '__fish_beet_using_command dup' -r -s m -l move -f -d 'move items to dest' +complete -c beet -n '__fish_beet_using_command dup' -r -s o -l copy -f -d 'copy items to dest' +complete -c beet -n '__fish_beet_using_command dup' -r -s t -l tag -f -d "tag matched items with 'k=v' attribute" +complete -c beet -n '__fish_beet_using_command dup' -s a -l album -f -d 'match albums instead of tracks' +complete -c beet -n '__fish_beet_using_command dup' -s p -l path -f -d 'print paths for matched items or albums' +complete -c beet -n '__fish_beet_using_command dup' -r -s f -l format -f -d 'print with custom format' +complete -c beet -n '__fish_beet_using_command dup' -s h -l help -f -d 'print help' + + +# ====== completions for duplicates ===== +complete -c beet -n '__fish_beet_using_command duplicates' -s c -l count -f -d 'show duplicate counts' +complete -c beet -n '__fish_beet_using_command duplicates' -r -s C -l checksum -f -d 'report duplicates based on arbitrary command' +complete -c beet -n '__fish_beet_using_command duplicates' -s d -l delete -f -d 'delete items from library and disk' +complete -c beet -n '__fish_beet_using_command duplicates' -s F -l full -f -d 'show all versions of duplicate tracks or albums' +complete -c beet -n '__fish_beet_using_command duplicates' -s s -l strict -f -d 'report duplicates only if all attributes are set' +complete -c beet -n '__fish_beet_using_command duplicates' -r -s k -l key -f -d 'report duplicates based on keys (use multiple times)' +complete -c beet -n '__fish_beet_using_command duplicates' -s M -l merge -f -d 'merge duplicate items' +complete -c beet -n '__fish_beet_using_command duplicates' -r -s m -l move -f -d 'move items to dest' +complete -c beet -n '__fish_beet_using_command duplicates' -r -s o -l copy -f -d 'copy items to dest' +complete -c beet -n '__fish_beet_using_command duplicates' -r -s t -l tag -f -d "tag matched items with 'k=v' attribute" +complete -c beet -n '__fish_beet_using_command duplicates' -s a -l album -f -d 'match albums instead of tracks' +complete -c beet -n '__fish_beet_using_command duplicates' -s p -l path -f -d 'print paths for matched items or albums' +complete -c beet -n '__fish_beet_using_command duplicates' -r -s f -l format -f -d 'print with custom format' +complete -c beet -n '__fish_beet_using_command duplicates' -s h -l help -f -d 'print help' + + +# ====== completions for embedart ===== +complete -c beet -n '__fish_beet_using_command embedart' -r -s f -l file -f -d 'the image file to embed' +complete -c beet -n '__fish_beet_using_command embedart' -s y -l yes -f -d 'skip confirmation' +complete -c beet -n '__fish_beet_using_command embedart' -s h -l help -f -d 'print help' + + +# ====== completions for extractart ===== +complete -c beet -n '__fish_beet_using_command extractart' -r -s o -f -d 'image output file' +complete -c beet -n '__fish_beet_using_command extractart' -r -s n -f -d 'image filename to create for all matched albums' +complete -c beet -n '__fish_beet_using_command extractart' -s a -f -d 'associate the extracted images with the album' +complete -c beet -n '__fish_beet_using_command extractart' -s h -l help -f -d 'print help' + + +# ====== completions for fetchart ===== +complete -c beet -n '__fish_beet_using_command fetchart' -s f -l force -f -d 're-download art when already present' +complete -c beet -n '__fish_beet_using_command fetchart' -s q -l quiet -f -d 'quiet mode: do not output albums that already have artwork' +complete -c beet -n '__fish_beet_using_command fetchart' -s h -l help -f -d 'print help' + + +# ====== completions for fields ===== +complete -c beet -n '__fish_beet_using_command fields' -s h -l help -f -d 'print help' + + +# ====== completions for fish ===== +complete -c beet -n '__fish_beet_using_command fish' -s f -l noFields -f -d 'omit album/track field completions' +complete -c beet -n '__fish_beet_using_command fish' -r -s e -l extravalues -f -a 'id path album_id title artist artist_sort artist_credit album albumartist albumartist_sort albumartist_credit genre style discogs_albumid discogs_artistid discogs_labelid lyricist composer composer_sort work mb_workid work_disambig arranger grouping year month day track tracktotal disc disctotal lyrics comments bpm comp mb_trackid mb_albumid mb_artistid mb_albumartistid mb_releasetrackid trackdisambig albumtype albumtypes label acoustid_fingerprint acoustid_id mb_releasegroupid asin isrc catalognum script language country albumstatus media albumdisambig releasegroupdisambig disctitle encoder rg_track_gain rg_track_peak rg_album_gain rg_album_peak r128_track_gain r128_album_gain original_year original_month original_day initial_key length bitrate format samplerate bitdepth channels mtime added singleton filesize id artpath added albumartist albumartist_sort albumartist_credit album genre style discogs_albumid discogs_artistid discogs_labelid year month day disctotal comp mb_albumid mb_albumartistid albumtype albumtypes label mb_releasegroupid asin catalognum script language country albumstatus albumdisambig releasegroupdisambig rg_album_gain rg_album_peak r128_album_gain original_year original_month original_day missing path albumtotal' -d 'include specified field *values* in completions' +complete -c beet -n '__fish_beet_using_command fish' -s h -l help -f -d 'print help' + + +# ====== completions for \? ===== +complete -c beet -n '__fish_beet_using_command \?' -s h -l help -f -d 'print help' + + +# ====== completions for help ===== +complete -c beet -n '__fish_beet_using_command help' -s h -l help -f -d 'print help' + + +# ====== completions for imp ===== +complete -c beet -n '__fish_beet_using_command imp' -s c -l copy -f -d 'copy tracks into library directory (default)' +complete -c beet -n '__fish_beet_using_command imp' -s C -l nocopy -f -d "don't copy tracks (opposite of -c)" +complete -c beet -n '__fish_beet_using_command imp' -s m -l move -f -d 'move tracks into the library (overrides -c)' +complete -c beet -n '__fish_beet_using_command imp' -s w -l write -f -d "write new metadata to files' tags (default)" +complete -c beet -n '__fish_beet_using_command imp' -s W -l nowrite -f -d "don't write metadata (opposite of -w)" +complete -c beet -n '__fish_beet_using_command imp' -s a -l autotag -f -d 'infer tags for imported files (default)' +complete -c beet -n '__fish_beet_using_command imp' -s A -l noautotag -f -d "don't infer tags for imported files (opposite of -a)" +complete -c beet -n '__fish_beet_using_command imp' -s p -l resume -f -d 'resume importing if interrupted' +complete -c beet -n '__fish_beet_using_command imp' -s P -l noresume -f -d 'do not try to resume importing' +complete -c beet -n '__fish_beet_using_command imp' -s q -l quiet -f -d 'never prompt for input: skip albums instead' +complete -c beet -n '__fish_beet_using_command imp' -r -s l -l log -f -d 'file to log untaggable albums for later review' +complete -c beet -n '__fish_beet_using_command imp' -s s -l singletons -f -d 'import individual tracks instead of full albums' +complete -c beet -n '__fish_beet_using_command imp' -s t -l timid -f -d 'always confirm all actions' +complete -c beet -n '__fish_beet_using_command imp' -s L -l library -f -d 'retag items matching a query' +complete -c beet -n '__fish_beet_using_command imp' -s i -l incremental -f -d 'skip already-imported directories' +complete -c beet -n '__fish_beet_using_command imp' -s I -l noincremental -f -d 'do not skip already-imported directories' +complete -c beet -n '__fish_beet_using_command imp' -l from-scratch -f -d 'erase existing metadata before applying new metadata' +complete -c beet -n '__fish_beet_using_command imp' -l flat -f -d 'import an entire tree as a single album' +complete -c beet -n '__fish_beet_using_command imp' -s g -l group-albums -f -d 'group tracks in a folder into separate albums' +complete -c beet -n '__fish_beet_using_command imp' -l pretend -f -d 'just print the files to import' +complete -c beet -n '__fish_beet_using_command imp' -r -s S -l search-id -f -d 'restrict matching to a specific metadata backend ID' +complete -c beet -n '__fish_beet_using_command imp' -r -l set -f -d 'set the given fields to the supplied values' +complete -c beet -n '__fish_beet_using_command imp' -s h -l help -f -d 'print help' + + +# ====== completions for im ===== +complete -c beet -n '__fish_beet_using_command im' -s c -l copy -f -d 'copy tracks into library directory (default)' +complete -c beet -n '__fish_beet_using_command im' -s C -l nocopy -f -d "don't copy tracks (opposite of -c)" +complete -c beet -n '__fish_beet_using_command im' -s m -l move -f -d 'move tracks into the library (overrides -c)' +complete -c beet -n '__fish_beet_using_command im' -s w -l write -f -d "write new metadata to files' tags (default)" +complete -c beet -n '__fish_beet_using_command im' -s W -l nowrite -f -d "don't write metadata (opposite of -w)" +complete -c beet -n '__fish_beet_using_command im' -s a -l autotag -f -d 'infer tags for imported files (default)' +complete -c beet -n '__fish_beet_using_command im' -s A -l noautotag -f -d "don't infer tags for imported files (opposite of -a)" +complete -c beet -n '__fish_beet_using_command im' -s p -l resume -f -d 'resume importing if interrupted' +complete -c beet -n '__fish_beet_using_command im' -s P -l noresume -f -d 'do not try to resume importing' +complete -c beet -n '__fish_beet_using_command im' -s q -l quiet -f -d 'never prompt for input: skip albums instead' +complete -c beet -n '__fish_beet_using_command im' -r -s l -l log -f -d 'file to log untaggable albums for later review' +complete -c beet -n '__fish_beet_using_command im' -s s -l singletons -f -d 'import individual tracks instead of full albums' +complete -c beet -n '__fish_beet_using_command im' -s t -l timid -f -d 'always confirm all actions' +complete -c beet -n '__fish_beet_using_command im' -s L -l library -f -d 'retag items matching a query' +complete -c beet -n '__fish_beet_using_command im' -s i -l incremental -f -d 'skip already-imported directories' +complete -c beet -n '__fish_beet_using_command im' -s I -l noincremental -f -d 'do not skip already-imported directories' +complete -c beet -n '__fish_beet_using_command im' -l from-scratch -f -d 'erase existing metadata before applying new metadata' +complete -c beet -n '__fish_beet_using_command im' -l flat -f -d 'import an entire tree as a single album' +complete -c beet -n '__fish_beet_using_command im' -s g -l group-albums -f -d 'group tracks in a folder into separate albums' +complete -c beet -n '__fish_beet_using_command im' -l pretend -f -d 'just print the files to import' +complete -c beet -n '__fish_beet_using_command im' -r -s S -l search-id -f -d 'restrict matching to a specific metadata backend ID' +complete -c beet -n '__fish_beet_using_command im' -r -l set -f -d 'set the given fields to the supplied values' +complete -c beet -n '__fish_beet_using_command im' -s h -l help -f -d 'print help' + + +# ====== completions for import ===== +complete -c beet -n '__fish_beet_using_command import' -s c -l copy -f -d 'copy tracks into library directory (default)' +complete -c beet -n '__fish_beet_using_command import' -s C -l nocopy -f -d "don't copy tracks (opposite of -c)" +complete -c beet -n '__fish_beet_using_command import' -s m -l move -f -d 'move tracks into the library (overrides -c)' +complete -c beet -n '__fish_beet_using_command import' -s w -l write -f -d "write new metadata to files' tags (default)" +complete -c beet -n '__fish_beet_using_command import' -s W -l nowrite -f -d "don't write metadata (opposite of -w)" +complete -c beet -n '__fish_beet_using_command import' -s a -l autotag -f -d 'infer tags for imported files (default)' +complete -c beet -n '__fish_beet_using_command import' -s A -l noautotag -f -d "don't infer tags for imported files (opposite of -a)" +complete -c beet -n '__fish_beet_using_command import' -s p -l resume -f -d 'resume importing if interrupted' +complete -c beet -n '__fish_beet_using_command import' -s P -l noresume -f -d 'do not try to resume importing' +complete -c beet -n '__fish_beet_using_command import' -s q -l quiet -f -d 'never prompt for input: skip albums instead' +complete -c beet -n '__fish_beet_using_command import' -r -s l -l log -f -d 'file to log untaggable albums for later review' +complete -c beet -n '__fish_beet_using_command import' -s s -l singletons -f -d 'import individual tracks instead of full albums' +complete -c beet -n '__fish_beet_using_command import' -s t -l timid -f -d 'always confirm all actions' +complete -c beet -n '__fish_beet_using_command import' -s L -l library -f -d 'retag items matching a query' +complete -c beet -n '__fish_beet_using_command import' -s i -l incremental -f -d 'skip already-imported directories' +complete -c beet -n '__fish_beet_using_command import' -s I -l noincremental -f -d 'do not skip already-imported directories' +complete -c beet -n '__fish_beet_using_command import' -l from-scratch -f -d 'erase existing metadata before applying new metadata' +complete -c beet -n '__fish_beet_using_command import' -l flat -f -d 'import an entire tree as a single album' +complete -c beet -n '__fish_beet_using_command import' -s g -l group-albums -f -d 'group tracks in a folder into separate albums' +complete -c beet -n '__fish_beet_using_command import' -l pretend -f -d 'just print the files to import' +complete -c beet -n '__fish_beet_using_command import' -r -s S -l search-id -f -d 'restrict matching to a specific metadata backend ID' +complete -c beet -n '__fish_beet_using_command import' -r -l set -f -d 'set the given fields to the supplied values' +complete -c beet -n '__fish_beet_using_command import' -s h -l help -f -d 'print help' + + +# ====== completions for info ===== +complete -c beet -n '__fish_beet_using_command info' -s l -l library -f -d 'show library fields instead of tags' +complete -c beet -n '__fish_beet_using_command info' -s a -l album -f -d 'show album fields instead of tracks (implies "--library")' +complete -c beet -n '__fish_beet_using_command info' -s s -l summarize -f -d 'summarize the tags of all files' +complete -c beet -n '__fish_beet_using_command info' -r -s i -l include-keys -f -d 'comma separated list of keys to show' +complete -c beet -n '__fish_beet_using_command info' -s k -l keys-only -f -d 'show only the keys' +complete -c beet -n '__fish_beet_using_command info' -r -s f -l format -f -d 'print with custom format' +complete -c beet -n '__fish_beet_using_command info' -s h -l help -f -d 'print help' + + +# ====== completions for ls ===== +complete -c beet -n '__fish_beet_using_command ls' -s a -l album -f -d 'match albums instead of tracks' +complete -c beet -n '__fish_beet_using_command ls' -s p -l path -f -d 'print paths for matched items or albums' +complete -c beet -n '__fish_beet_using_command ls' -r -s f -l format -f -d 'print with custom format' +complete -c beet -n '__fish_beet_using_command ls' -s h -l help -f -d 'print help' + + +# ====== completions for list ===== +complete -c beet -n '__fish_beet_using_command list' -s a -l album -f -d 'match albums instead of tracks' +complete -c beet -n '__fish_beet_using_command list' -s p -l path -f -d 'print paths for matched items or albums' +complete -c beet -n '__fish_beet_using_command list' -r -s f -l format -f -d 'print with custom format' +complete -c beet -n '__fish_beet_using_command list' -s h -l help -f -d 'print help' + + +# ====== completions for miss ===== +complete -c beet -n '__fish_beet_using_command miss' -s c -l count -f -d 'count missing tracks per album' +complete -c beet -n '__fish_beet_using_command miss' -s t -l total -f -d 'count total of missing tracks' +complete -c beet -n '__fish_beet_using_command miss' -s a -l album -f -d 'show missing albums for artist instead of tracks' +complete -c beet -n '__fish_beet_using_command miss' -r -s f -l format -f -d 'print with custom format' +complete -c beet -n '__fish_beet_using_command miss' -s h -l help -f -d 'print help' + + +# ====== completions for missing ===== +complete -c beet -n '__fish_beet_using_command missing' -s c -l count -f -d 'count missing tracks per album' +complete -c beet -n '__fish_beet_using_command missing' -s t -l total -f -d 'count total of missing tracks' +complete -c beet -n '__fish_beet_using_command missing' -s a -l album -f -d 'show missing albums for artist instead of tracks' +complete -c beet -n '__fish_beet_using_command missing' -r -s f -l format -f -d 'print with custom format' +complete -c beet -n '__fish_beet_using_command missing' -s h -l help -f -d 'print help' + + +# ====== completions for mod ===== +complete -c beet -n '__fish_beet_using_command mod' -s m -l move -f -d 'move files in the library directory' +complete -c beet -n '__fish_beet_using_command mod' -s M -l nomove -f -d "don't move files in library" +complete -c beet -n '__fish_beet_using_command mod' -s w -l write -f -d "write new metadata to files' tags (default)" +complete -c beet -n '__fish_beet_using_command mod' -s W -l nowrite -f -d "don't write metadata (opposite of -w)" +complete -c beet -n '__fish_beet_using_command mod' -s a -l album -f -d 'match albums instead of tracks' +complete -c beet -n '__fish_beet_using_command mod' -r -s f -l format -f -d 'print with custom format' +complete -c beet -n '__fish_beet_using_command mod' -s y -l yes -f -d 'skip confirmation' +complete -c beet -n '__fish_beet_using_command mod' -s h -l help -f -d 'print help' + + +# ====== completions for modify ===== +complete -c beet -n '__fish_beet_using_command modify' -s m -l move -f -d 'move files in the library directory' +complete -c beet -n '__fish_beet_using_command modify' -s M -l nomove -f -d "don't move files in library" +complete -c beet -n '__fish_beet_using_command modify' -s w -l write -f -d "write new metadata to files' tags (default)" +complete -c beet -n '__fish_beet_using_command modify' -s W -l nowrite -f -d "don't write metadata (opposite of -w)" +complete -c beet -n '__fish_beet_using_command modify' -s a -l album -f -d 'match albums instead of tracks' +complete -c beet -n '__fish_beet_using_command modify' -r -s f -l format -f -d 'print with custom format' +complete -c beet -n '__fish_beet_using_command modify' -s y -l yes -f -d 'skip confirmation' +complete -c beet -n '__fish_beet_using_command modify' -s h -l help -f -d 'print help' + + +# ====== completions for mv ===== +complete -c beet -n '__fish_beet_using_command mv' -r -s d -l dest -f -d 'destination directory' +complete -c beet -n '__fish_beet_using_command mv' -s c -l copy -f -d 'copy instead of moving' +complete -c beet -n '__fish_beet_using_command mv' -s p -l pretend -f -d "show how files would be moved, but don't touch anything" +complete -c beet -n '__fish_beet_using_command mv' -s t -l timid -f -d 'always confirm all actions' +complete -c beet -n '__fish_beet_using_command mv' -s e -l export -f -d 'copy without changing the database path' +complete -c beet -n '__fish_beet_using_command mv' -s a -l album -f -d 'match albums instead of tracks' +complete -c beet -n '__fish_beet_using_command mv' -s h -l help -f -d 'print help' + + +# ====== completions for move ===== +complete -c beet -n '__fish_beet_using_command move' -r -s d -l dest -f -d 'destination directory' +complete -c beet -n '__fish_beet_using_command move' -s c -l copy -f -d 'copy instead of moving' +complete -c beet -n '__fish_beet_using_command move' -s p -l pretend -f -d "show how files would be moved, but don't touch anything" +complete -c beet -n '__fish_beet_using_command move' -s t -l timid -f -d 'always confirm all actions' +complete -c beet -n '__fish_beet_using_command move' -s e -l export -f -d 'copy without changing the database path' +complete -c beet -n '__fish_beet_using_command move' -s a -l album -f -d 'match albums instead of tracks' +complete -c beet -n '__fish_beet_using_command move' -s h -l help -f -d 'print help' + + +# ====== completions for rm ===== +complete -c beet -n '__fish_beet_using_command rm' -s d -l delete -f -d 'also remove files from disk' +complete -c beet -n '__fish_beet_using_command rm' -s f -l force -f -d 'do not ask when removing items' +complete -c beet -n '__fish_beet_using_command rm' -s a -l album -f -d 'match albums instead of tracks' +complete -c beet -n '__fish_beet_using_command rm' -s h -l help -f -d 'print help' + + +# ====== completions for remove ===== +complete -c beet -n '__fish_beet_using_command remove' -s d -l delete -f -d 'also remove files from disk' +complete -c beet -n '__fish_beet_using_command remove' -s f -l force -f -d 'do not ask when removing items' +complete -c beet -n '__fish_beet_using_command remove' -s a -l album -f -d 'match albums instead of tracks' +complete -c beet -n '__fish_beet_using_command remove' -s h -l help -f -d 'print help' + + +# ====== completions for spotify ===== +complete -c beet -n '__fish_beet_using_command spotify' -r -s m -l mode -f -d '"open" to open Spotify with playlist, "list" to print (default)' +complete -c beet -n '__fish_beet_using_command spotify' -s f -l show-failures -f -d 'list tracks that did not match a Spotify ID' +complete -c beet -n '__fish_beet_using_command spotify' -s h -l help -f -d 'print help' + + +# ====== completions for stats ===== +complete -c beet -n '__fish_beet_using_command stats' -s e -l exact -f -d 'exact size and time' +complete -c beet -n '__fish_beet_using_command stats' -s h -l help -f -d 'print help' + + +# ====== completions for upd ===== +complete -c beet -n '__fish_beet_using_command upd' -s a -l album -f -d 'match albums instead of tracks' +complete -c beet -n '__fish_beet_using_command upd' -r -s f -l format -f -d 'print with custom format' +complete -c beet -n '__fish_beet_using_command upd' -s m -l move -f -d 'move files in the library directory' +complete -c beet -n '__fish_beet_using_command upd' -s M -l nomove -f -d "don't move files in library" +complete -c beet -n '__fish_beet_using_command upd' -s p -l pretend -f -d 'show all changes but do nothing' +complete -c beet -n '__fish_beet_using_command upd' -r -s F -l field -f -d 'list of fields to update' +complete -c beet -n '__fish_beet_using_command upd' -s h -l help -f -d 'print help' + + +# ====== completions for up ===== +complete -c beet -n '__fish_beet_using_command up' -s a -l album -f -d 'match albums instead of tracks' +complete -c beet -n '__fish_beet_using_command up' -r -s f -l format -f -d 'print with custom format' +complete -c beet -n '__fish_beet_using_command up' -s m -l move -f -d 'move files in the library directory' +complete -c beet -n '__fish_beet_using_command up' -s M -l nomove -f -d "don't move files in library" +complete -c beet -n '__fish_beet_using_command up' -s p -l pretend -f -d 'show all changes but do nothing' +complete -c beet -n '__fish_beet_using_command up' -r -s F -l field -f -d 'list of fields to update' +complete -c beet -n '__fish_beet_using_command up' -s h -l help -f -d 'print help' + + +# ====== completions for update ===== +complete -c beet -n '__fish_beet_using_command update' -s a -l album -f -d 'match albums instead of tracks' +complete -c beet -n '__fish_beet_using_command update' -r -s f -l format -f -d 'print with custom format' +complete -c beet -n '__fish_beet_using_command update' -s m -l move -f -d 'move files in the library directory' +complete -c beet -n '__fish_beet_using_command update' -s M -l nomove -f -d "don't move files in library" +complete -c beet -n '__fish_beet_using_command update' -s p -l pretend -f -d 'show all changes but do nothing' +complete -c beet -n '__fish_beet_using_command update' -r -s F -l field -f -d 'list of fields to update' +complete -c beet -n '__fish_beet_using_command update' -s h -l help -f -d 'print help' + + +# ====== completions for version ===== +complete -c beet -n '__fish_beet_using_command version' -s h -l help -f -d 'print help' + + +# ====== completions for write ===== +complete -c beet -n '__fish_beet_using_command write' -s p -l pretend -f -d 'show all changes but do nothing' +complete -c beet -n '__fish_beet_using_command write' -s f -l force -f -d 'write tags even if the existing tags match the database' +complete -c beet -n '__fish_beet_using_command write' -s h -l help -f -d 'print help'
\ No newline at end of file diff --git a/.config/fish/config.fish b/.config/fish/config.fish index 2fddce3..59a861c 100644 --- a/.config/fish/config.fish +++ b/.config/fish/config.fish @@ -1,12 +1,9 @@ if not status is-interactive exit end - -# fish -set -gx FISH_CFG $XDG_CONFIG_HOME/fish -set -g fish_greeting -source $FISH_CFG/path.fish -set -gx LANG "en_US.UTF-8" +# for agent in $(pidof ssh-agent | string split " ") +# kill $agent +# end # xdg set -gx XDG_CONFIG_HOME "$HOME/.config" @@ -14,15 +11,39 @@ set -gx XDG_CACHE_HOME "$HOME/.cache" set -gx XDG_DATA_HOME "$HOME/.local/share" set -gx XDG_STATE_HOME "$HOME/.local/state" +# fish +set -gx FISH_CFG $XDG_CONFIG_HOME/fish +set -g fish_greeting +set -gx LANG "en_US.UTF-8" + +# home pollution fixes +set -gx CARGO_HOME "$XDG_DATA_HOME/cargo" +set -gx RUSTUP_HOME "$XDG_DATA_HOME/rustup" +set -gx GOPATH "$XDG_DATA_HOME/go" +set -gx GRADLE_USER_HOME "$XDG_DATA_HOME/gradle" +set -gx GTK2_RC_FILES "$XDG_CONFIG_HOME/gtk-2.0/gtkrc" +set -gx XCURSOR_PATH "/usr/share/icons:$XDG_DATA_HOME/icons" +set -gx LESSHISTFILE "$XDG_STATE_HOME/less/history" +set -gx NODE_REPL_HISTORY "$XDG_DATA_HOME/node_repl_history" +set -gx NPM_CONFIG_USERCONFIG "$XDG_CONFIG_HOME/npm/npmrc" +set -gx _JAVA_OPTIONS "-Djava.util.prefs.userRoot=$XDG_CONFIG_HOME/java" +set -gx NUGET_PACKAGES "$XDG_CACHE_HOME/NuGetPackages" +set -gx PASSWORD_STORE_DIR "$XDG_DATA_HOME/pass" +set -gx PYTHONSTARTUP "/etc/python/pythonrc" +set -gx W3M_DIR "$XDG_DATA_HOME/w3m" +set -gx PNPM_HOME "$HOME/.local/share/pnpm" + # program set -gx EDITOR "nvim" -set -gx VISUAL "lite-xl" +# set -gx VISUAL "lite-xl" +set -gx MANPAGER "nvim +Man!" +set -gx MANWIDTH 999 set -gx PF_INFO "ascii title os wm editor shell kernel palette" set -gx BAT_THEME "ansi" -set -gx PNPM_HOME "$HOME/.local/share/pnpm" -set -gx GTK_USE_PORTAL 1 set -gx LITE_SCALE 1.3 set -gx RANGER_LOAD_DEFAULT_RC "FALSE" +# set -gx MANPATH /usr/local/texlive/2023/texmf-dist/doc/man +set -gx INFOPATH /usr/local/texlive/2023/texmf-dist/doc/info # $PATH source $FISH_CFG/path.fish @@ -36,20 +57,44 @@ source $FISH_CFG/bindings.fish # configs source $FISH_CFG/done_cfg.fish +# source /opt/asdf-vm/asdf.fish + # abbreviations & aliases abbr -a paru paru --limit 10 -abbr -a projects cd ~/DesktopTemp/RealProjects +abbr -a projects cd ~/Documents/RealProjects abbr -a gc git commit -m abbr -a ga git add abbr -a gcl git clone +abbr -a pq pacman -Qqi +abbr -a gt git log --oneline --graph --decorate --all alias imgcat="wezterm imgcat" alias dots="git --git-dir=$HOME/.dots --work-tree=$HOME" -alias sudo="doas" +# alias sudo="doas" alias ls="ls --color=auto" +alias grep="grep -P --color=always" +alias molest="sudo touch" +alias wget="wget --hsts-file=$XDG_DATA_HOME/wget-hsts" +alias stylua="stylua -s" +alias cd="z" +alias n="nvim ." +function y + set tmp (mktemp -t "yazi-cwd.XXXXXX") + yazi $argv --cwd-file="$tmp" + if set cwd (command cat -- "$tmp"); and [ -n "$cwd" ]; and [ "$cwd" != "$PWD" ] + builtin cd -- "$cwd" + end + rm -f -- "$tmp" +end function ssh export TERM=xterm-color /usr/bin/ssh $argv - export TERM=xterm-kitty + export TERM=xterm-256color +end +function flac_preview + # nsxiv (ffmpeg -i $argv[1] -v 8 -t 30 -lavfi showspectrumpic=s=hd720 -f image2 pipe: | psub) + nsxiv (ffmpeg -i $argv[1] -v 8 -t 30 -lavfi showspectrumpic=s=2k -f image2 pipe: | psub) + + end function multicd echo (string repeat -n (math (string length -- $argv[1]) - 1) ../) @@ -58,8 +103,16 @@ abbr -a dotdot --regex '^\.\.+$' --position anywhere --function multicd function last_history_item echo $history[1] end +function post_exec_line --on-event fish_postexec + if not string match -qr "^clear" $argv[1] + echo "" + end +end abbr -a !! --position anywhere --function last_history_item # shell init -eval "$(ssh-agent -c)" +# eval "$(ssh-agent -c)" &>/dev/null +fish_ssh_agent starship init fish | source +zoxide init fish | source +direnv hook fish | source diff --git a/.config/fish/fish_variables b/.config/fish/fish_variables index fc6415f..2ac8f35 100644 --- a/.config/fish/fish_variables +++ b/.config/fish/fish_variables @@ -1,11 +1,11 @@ # This file contains fish universal variable definitions. # VERSION: 3.0 SETUVAR __done_exclude:lite\x2dxl -SETUVAR __fish_initialized:3400 -SETUVAR fish_color_autosuggestion:\x238893a5 +SETUVAR __fish_initialized:3800 +SETUVAR fish_color_autosuggestion:\x2377828c SETUVAR fish_color_cancel:\x2dr SETUVAR fish_color_command:white -SETUVAR fish_color_comment:\x238893a5 +SETUVAR fish_color_comment:\x2377828c SETUVAR fish_color_cwd:green SETUVAR fish_color_cwd_root:red SETUVAR fish_color_end:pink @@ -26,9 +26,9 @@ SETUVAR fish_color_user:brgreen SETUVAR fish_color_valid_path:white SETUVAR fish_greeting:\x1d SETUVAR fish_key_bindings:fish_default_key_bindings -SETUVAR fish_pager_color_completion:\x238893a5 -SETUVAR fish_pager_color_description:\x238893a5 +SETUVAR fish_pager_color_completion:\x2377828c +SETUVAR fish_pager_color_description:\x2377828c SETUVAR fish_pager_color_prefix:white -SETUVAR fish_pager_color_progress:\x238893a5 +SETUVAR fish_pager_color_progress:\x2377828c SETUVAR fish_pager_color_selected_background:\x2d\x2dbackground\x3dblack -SETUVAR fish_user_paths:/home/delta/\x2elocal/share/pnpm\x1e/home/delta/\x2elocal/bin\x1e/home/delta/\x2ecargo/bin\x1e/home/delta/\x2elocal/share/gem/ruby/3\x2e0\x2e0/bin\x1e/home/delta/\x2espicetify\x1e/home/delta/go/bin\x1e/home/delta/\x2edeno/bin +SETUVAR fish_user_paths:/home/delta/\x2elocal/share/pnpm\x1e/home/delta/\x2elocal/bin\x1e/home/delta/\x2elocal/share/cargo/bin\x1e/home/delta/\x2elocal/share/gem/ruby/3\x2e0\x2e0/bin\x1e/home/delta/\x2elocal/share/go/bin\x1e/home/delta/\x2edetaspace/bin\x1e/usr/local/texlive/2023/bin/x86_64\x2dlinux diff --git a/.config/fish/functions/fish_commandline_toggle.fish b/.config/fish/functions/fish_commandline_toggle.fish new file mode 100644 index 0000000..688c97e --- /dev/null +++ b/.config/fish/functions/fish_commandline_toggle.fish @@ -0,0 +1,26 @@ +function __commandline_stash -d 'Stash current command line' + set -g __stash_command_position (commandline -C) + set -g __stash_command (commandline -b) + commandline -r "" +end + +function __commandline_pop -d 'Pop last stashed command line' + if not set -q __stash_command + return + end + commandline -r $__stash_command + if set -q __stash_command_position + commandline -C $__stash_command_position + end + set -e __stash_command + set -e __stash_command_position +end + +function fish_commandline_toggle -d 'Stash current commandline if not empty, otherwise pop last stashed commandline' + set -l cmd (commandline -b) + if test "$cmd" + __commandline_stash + else + __commandline_pop + end +end diff --git a/.config/fish/functions/fish_ssh_agent.fish b/.config/fish/functions/fish_ssh_agent.fish new file mode 100644 index 0000000..5960b75 --- /dev/null +++ b/.config/fish/functions/fish_ssh_agent.fish @@ -0,0 +1,32 @@ +function __ssh_agent_is_started -d "check if ssh agent is already started" + if begin; test -f $SSH_ENV; and test -z "$SSH_AGENT_PID"; end + source $SSH_ENV > /dev/null + end + + if test -z "$SSH_AGENT_PID" + return 1 + end + + ps -ef | grep $SSH_AGENT_PID | grep -v grep | grep -q ssh-agent + #pgrep ssh-agent + return $status +end + + +function __ssh_agent_start -d "start a new ssh agent" + ssh-agent -c | sed 's/^echo/#echo/' > $SSH_ENV + chmod 600 $SSH_ENV + source $SSH_ENV > /dev/null + true # suppress errors from setenv, i.e. set -gx +end + + +function fish_ssh_agent --description "Start ssh-agent if not started yet, or uses already started ssh-agent." + if test -z "$SSH_ENV" + set -xg SSH_ENV $HOME/.ssh/environment + end + + if not __ssh_agent_is_started + __ssh_agent_start + end +end diff --git a/.config/fish/path.fish b/.config/fish/path.fish index d0d3144..b977e7c 100644 --- a/.config/fish/path.fish +++ b/.config/fish/path.fish @@ -1,20 +1,14 @@ alias fp="fish_add_path -a" -# function pa -# set -x PATH PATH $argv[1] -# end -# -# function pp -# set -x PATH $argv[1] PATH -# end - -# ik this is not the "correct" way to set path but persistent path sucks +#set -gx PATH $PNPM_HOME ~/.local/bin $CARGO_HOME/bin ~/.local/share/gem/ruby/3.0.0/bin $GOPATH/bin ~/.detaspace/bin $PATH fp $PNPM_HOME -fp ~/.deno/bin fp ~/.local/bin fp ~/usr/bin -fp ~/.cargo/bin +fp $CARGO_HOME/bin fp ~/.local/share/gem/ruby/3.0.0/bin -fp ~/.spicetify -fp ~/go/bin +#fp ~/.spicetify +fp $GOPATH/bin +fp ~/.detaspace/bin +fp /usr/local/texlive/2023/bin/x86_64-linux +fp ~/.nix-profile/bin diff --git a/.config/nvim/.luarc.json b/.config/nvim/.luarc.json new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/.config/nvim/.luarc.json diff --git a/.config/nvim/.nvim.lua b/.config/nvim/.nvim.lua new file mode 100644 index 0000000..9c7049d --- /dev/null +++ b/.config/nvim/.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/nvim/init.lua b/.config/nvim/init.lua index b74b4af..91bfca2 100644 --- a/.config/nvim/init.lua +++ b/.config/nvim/init.lua @@ -2,4 +2,13 @@ local lazypath = vim.fn.stdpath "data" .. "/lazy/lazy.nvim" vim.opt.rtp:prepend(lazypath) require "options" -require("lazy").setup "plugins" +require "cmds" +require "autocmd" +require "binds" +require("lazy").setup { + spec = "plugins", + dev = { + path = vim.fn.stdpath "config" .. "/lua/local_plugins" + } +} +vim.cmd[[colo prismite]] diff --git a/.config/nvim/lazy-lock.json b/.config/nvim/lazy-lock.json index ff8a865..229bff2 100644 --- a/.config/nvim/lazy-lock.json +++ b/.config/nvim/lazy-lock.json @@ -1,17 +1,35 @@ { - "alpha-nvim": { "branch": "main", "commit": "63a860e7ed3ae41ee92481ea65a48fb35431ae21" }, - "better-escape.nvim": { "branch": "master", "commit": "7031dc734add47bb71c010e0551829fa5799375f" }, - "dressing.nvim": { "branch": "master", "commit": "ee571505f3566f84fd252e76c4ce6df6eaf2fb94" }, - "indent-blankline.nvim": { "branch": "master", "commit": "9637670896b68805430e2f72cf5d16be5b97a22a" }, - "lazy.nvim": { "branch": "main", "commit": "dac844ed617dda4f9ec85eb88e9629ad2add5e05" }, - "lualine.nvim": { "branch": "master", "commit": "45e27ca739c7be6c49e5496d14fcf45a303c3a63" }, - "mini.move": { "branch": "main", "commit": "3afd39873eb9171684e554a214c055482444a47d" }, - "neo-tree.nvim": { "branch": "main", "commit": "8a0f795bac6618e4fe59eda61b15f8c95d9625ad" }, - "noice.nvim": { "branch": "main", "commit": "894db25ec726d32047799d4d0a982b701bec453b" }, - "nui.nvim": { "branch": "main", "commit": "9e3916e784660f55f47daa6f26053ad044db5d6a" }, - "nvim-treesitter": { "branch": "master", "commit": "d0b17cc0b9c8c3055530770a9dd4de659232c692" }, - "nvim-web-devicons": { "branch": "master", "commit": "cfc8824cc1db316a276b36517f093baccb8e799a" }, - "plenary.nvim": { "branch": "master", "commit": "0dbe561ae023f02c2fb772b879e905055b939ce3" }, - "telescope.nvim": { "branch": "master", "commit": "207285ccec21b69996a4d3bcfa59df35d48610e8" }, - "tokyonight.nvim": { "branch": "main", "commit": "1ee11019f8a81dac989ae1db1a013e3d582e2033" } -}
\ No newline at end of file + "LuaSnip": { "branch": "master", "commit": "fb525166ccc30296fb3457441eb979113de46b00" }, + "alpha-nvim": { "branch": "main", "commit": "a35468cd72645dbd52c0624ceead5f301c566dff" }, + "better-escape.nvim": { "branch": "master", "commit": "19a38aab94961016430905ebec30d272a01e9742" }, + "cmp-nvim-lsp": { "branch": "main", "commit": "a8912b88ce488f411177fc8aed358b04dc246d7b" }, + "cmp-path": { "branch": "main", "commit": "c6635aae33a50d6010bf1aa756ac2398a2d54c32" }, + "cmp_luasnip": { "branch": "master", "commit": "98d9cb5c2c38532bd9bdb481067b20fea8f32e90" }, + "dressing.nvim": { "branch": "master", "commit": "2d7c2db2507fa3c4956142ee607431ddb2828639" }, + "git-blame.nvim": { "branch": "master", "commit": "8503b199edf9a666fe7b1a989cf14e3c26b2eb03" }, + "indentmini.nvim": { "branch": "main", "commit": "6211f93b0c8161d2a2b4000b9bf0c01c0a115455" }, + "lazy.nvim": { "branch": "main", "commit": "6c3bda4aca61a13a9c63f1c1d1b16b9d3be90d7a" }, + "leap.nvim": { "branch": "main", "commit": "10c14af4ddfb34dbd7721f0bfb2b4d91f0558907" }, + "lualine.nvim": { "branch": "master", "commit": "a94fc68960665e54408fe37dcf573193c4ce82c9" }, + "marks.nvim": { "branch": "master", "commit": "f353e8c08c50f39e99a9ed474172df7eddd89b72" }, + "mason-lspconfig.nvim": { "branch": "main", "commit": "f54e3c11fc9ebfcfc27e696182b0295b071d0811" }, + "mason.nvim": { "branch": "main", "commit": "8024d64e1330b86044fed4c8494ef3dcd483a67c" }, + "mini.colors": { "branch": "main", "commit": "ef76867adda63d6010acdc8732a816c8527d276b" }, + "mini.comment": { "branch": "main", "commit": "51c173dffa17dc14c81169deaeea430bd394ab51" }, + "mini.surround": { "branch": "main", "commit": "1a2b59c77a0c4713a5bd8972da322f842f4821b1" }, + "noice.nvim": { "branch": "main", "commit": "0427460c2d7f673ad60eb02b35f5e9926cf67c59" }, + "nui.nvim": { "branch": "main", "commit": "de740991c12411b663994b2860f1a4fd0937c130" }, + "nvim-cmp": { "branch": "main", "commit": "b5311ab3ed9c846b585c0c15b7559be131ec4be9" }, + "nvim-cokeline": { "branch": "main", "commit": "9fbed130683b7b6f73198c09e35ba4b33f547c08" }, + "nvim-colorizer.lua": { "branch": "master", "commit": "517df88cf2afb36652830df2c655df2da416a0ae" }, + "nvim-lspconfig": { "branch": "master", "commit": "314b35335cc84bc2a085c84c69da955ba22da163" }, + "nvim-notify": { "branch": "master", "commit": "b5825cf9ee881dd8e43309c93374ed5b87b7a896" }, + "nvim-tree.lua": { "branch": "master", "commit": "0a06f65bf06157972f20ca1dee03c97a0efcb188" }, + "nvim-treesitter": { "branch": "master", "commit": "42fc28ba918343ebfd5565147a42a26580579482" }, + "nvim-web-devicons": { "branch": "master", "commit": "1fb58cca9aebbc4fd32b086cb413548ce132c127" }, + "plenary.nvim": { "branch": "master", "commit": "857c5ac632080dba10aae49dba902ce3abf91b35" }, + "sessions.nvim": { "branch": "master", "commit": "f13158483e0b6255c6dfe473145ce4ee3495d844" }, + "telescope.nvim": { "branch": "master", "commit": "b4da76be54691e854d3e0e02c36b0245f945c2c7" }, + "trouble.nvim": { "branch": "main", "commit": "85bedb7eb7fa331a2ccbecb9202d8abba64d37b3" }, + "vim-repeat": { "branch": "master", "commit": "65846025c15494983dafe5e3b46c8f88ab2e9635" } +} diff --git a/.config/nvim/lua/autocmd.lua b/.config/nvim/lua/autocmd.lua new file mode 100644 index 0000000..f13e2e9 --- /dev/null +++ b/.config/nvim/lua/autocmd.lua @@ -0,0 +1,34 @@ +local group = vim.api.nvim_create_augroup("PrismiteNvim", { clear = true }) +vim.api.nvim_create_autocmd({ "BufEnter", "BufWinEnter" }, { + group = group, + callback = function() + if require("nvim-tree.utils").is_nvim_tree_buf() then + vim.cmd "stopinsert" + end + end, +}) + +-- By https://github.com/marvinth01, taken from https://github.com/nvim-tree/nvim-tree.lua/wiki/Auto-Close#marvinth01 +vim.api.nvim_create_autocmd("QuitPre", { + group = group, + callback = function() + local tree_wins = {} + local floating_wins = {} + local wins = vim.api.nvim_list_wins() + for _, w in ipairs(wins) do + local bufname = vim.api.nvim_buf_get_name(vim.api.nvim_win_get_buf(w)) + if bufname:match "NvimTree_" ~= nil then + table.insert(tree_wins, w) + end + if vim.api.nvim_win_get_config(w).relative ~= "" then + table.insert(floating_wins, w) + end + end + if 1 == #wins - #floating_wins - #tree_wins then + -- Should quit, so we close all invalid windows. + for _, w in ipairs(tree_wins) do + vim.api.nvim_win_close(w, true) + end + end + end, +}) diff --git a/.config/nvim/lua/binds.lua b/.config/nvim/lua/binds.lua new file mode 100644 index 0000000..55da905 --- /dev/null +++ b/.config/nvim/lua/binds.lua @@ -0,0 +1,54 @@ +local map = vim.keymap.set +-- local color_converter = require "color_converter" + +-- toggle nvim-tree +map("n", "<Leader>t", "<cmd>NvimTreeToggle<CR>") +-- toggle trouble +map("n", "<Leader>e", "<cmd>TroubleToggle<CR>") +-- undo +map("n", "U", "<C-r>") + +map("c", "<CR>", function() + if vim.fn.pumvisible() == 1 then + return "<C-y>" + end + return "<CR>" +end, { expr = true }) + +map("n", "H", "5h") +map("n", "J", "5j") +map("n", "K", "5k") +map("n", "L", "5l") + +-- disable arrow keys +map("n", "<UP>", "<NOP>") +map("n", "<DOWN>", "<NOP>") +map("n", "<LEFT>", "<NOP>") +map("n", "<RIGHT>", "<NOP>") + +map("i", "<UP>", "<NOP>") +map("i", "<DOWN>", "<NOP>") +map("i", "<LEFT>", "<NOP>") +map("i", "<RIGHT>", "<NOP>") + +map("n", "<Leader>h", vim.lsp.buf.hover) +map("n", "<Leader>gd", vim.lsp.buf.definition) + +map("n", "<leader>bp", function() + require('cokeline.mappings').pick("focus") +end, { desc = "Pick a buffer to focus" }) +map("n", "<leader>bq", function() + require('cokeline.mappings').pick("close") +end, { desc = "Pick a buffer to close" }) +map("n", "<Leader>p", function () + require("cokeline.mappings").by_step("switch", -1) +end, { silent = true }) +map("n", "<Leader>n", function () + require("cokeline.mappings").by_step("switch", 1) +end, { silent = true }) + + +-- local function setup_lsp_keys(_client, buffer) +-- map("<Leader>d", vim.diagnostic.open_float, "Line diagnostics") +-- +-- end diff --git a/.config/nvim/lua/cmds.lua b/.config/nvim/lua/cmds.lua new file mode 100644 index 0000000..1426b99 --- /dev/null +++ b/.config/nvim/lua/cmds.lua @@ -0,0 +1,20 @@ +local M = {} +local fn = vim.fn + +local function cabbrev(input, replace) + local cmd = 'cnoreabbrev <expr> %s v:lua.cmds.command("%s", "%s")' + + vim.cmd(cmd:format(input, input, replace)) +end + +function M.command(cmd, match) + if fn.getcmdtype() == ":" and fn.getcmdline():match("^" .. cmd) then + return match + else + return cmd + end +end + +cabbrev("vs", "vert sb") + +return M diff --git a/.config/nvim/lua/dash.lua b/.config/nvim/lua/dash.lua new file mode 100644 index 0000000..fd9f03c --- /dev/null +++ b/.config/nvim/lua/dash.lua @@ -0,0 +1,147 @@ +local lazy = require "lazy" +local alpha = require "alpha" + +vim.api.nvim_create_autocmd("ColorScheme", { + callback = function() + local groups = { + Red = "#ff928a", + Orange = "#ff9f6f", + Yellow = "#ecb256", + Green = "#8bd294", + Cyan = "#6dd3c2", + Blue = "#8bc3fc", + Purple = "#c4b1f6", + Pink = "#e5acb4", + } + for group, color in pairs(groups) do + vim.api.nvim_set_hl(0, "Dash" .. group, { fg = color, bold = true }) + end + local comment = vim.api.nvim_get_hl(0, { name = "Comment" }) + vim.api.nvim_set_hl(0, "DashEmphasis", vim.tbl_deep_extend("keep", comment, { bold = true })) + end, +}) + + +return { + layout = { + { + type = "padding", + val = 4, + }, + { + type = "text", + val = { + [[ ___ ___ ___ ___ ___ ___ ___ ___ ]], + [[ /\ \ /\ \ /\ \ /\ \ /\__\ /\ \ /\ \ /\ \ ]], + [[ /::\ \ /::\ \ _\:\ \ /::\ \ /::L_L_ _\:\ \ \:\ \ /::\ \ ]], + [[ /::\:\__\ /::\:\__\ /\/::\__\ /\:\:\__\ /:/L:\__\ /\/::\__\ /::\__\ /::\:\__\ ]], + [[ \/\::/ / \;:::/ / \::/\/__/ \:\:\/__/ \/_/:/ / \::/\/__/ /:/\/__/ \:\:\/ / ]], + [[ \/__/ |:\/__/ \:\__\ \::/ / /:/ / \:\__\ \/__/ \:\/ / ]], + [[ \|__| \/__/ \/__/ \/__/ \/__/ \/__/ ]], + [[ ]], + }, + opts = { + position = "center", + hl = { + { + { "DashRed", 1, 10 }, + { "DashOrange", 10, 20 }, + { "DashYellow", 20, 30 }, + { "DashGreen", 30, 40 }, + { "DashCyan", 40, 50 }, + { "DashBlue", 50, 60 }, + { "DashPurple", 60, 70 }, + { "DashPink", 70, 80 }, + }, + { + { "DashRed", 1, 10 }, + { "DashOrange", 10, 20 }, + { "DashYellow", 20, 30 }, + { "DashGreen", 30, 40 }, + { "DashCyan", 40, 50 }, + { "DashBlue", 50, 60 }, + { "DashPurple", 60, 70 }, + { "DashPink", 70, 80 }, + }, + { + { "DashRed", 1, 10 }, + { "DashOrange", 10, 20 }, + { "DashYellow", 20, 30 }, + { "DashGreen", 30, 40 }, + { "DashCyan", 40, 50 }, + { "DashBlue", 50, 60 }, + { "DashPurple", 60, 70 }, + { "DashPink", 70, 80 }, + }, + { + { "DashRed", 1, 10 }, + { "DashOrange", 10, 20 }, + { "DashYellow", 20, 30 }, + { "DashGreen", 30, 40 }, + { "DashCyan", 40, 50 }, + { "DashBlue", 50, 60 }, + { "DashPurple", 60, 70 }, + { "DashPink", 70, 80 }, + }, + { + { "DashRed", 1, 10 }, + { "DashOrange", 10, 20 }, + { "DashYellow", 20, 30 }, + { "DashGreen", 30, 40 }, + { "DashCyan", 40, 50 }, + { "DashBlue", 50, 60 }, + { "DashPurple", 60, 70 }, + { "DashPink", 70, 80 }, + }, + { + { "DashRed", 1, 10 }, + { "DashOrange", 10, 20 }, + { "DashYellow", 20, 30 }, + { "DashGreen", 30, 40 }, + { "DashCyan", 40, 50 }, + { "DashBlue", 50, 60 }, + { "DashPurple", 60, 70 }, + { "DashPink", 70, 80 }, + }, + { + { "DashRed", 1, 10 }, + { "DashOrange", 10, 20 }, + { "DashYellow", 20, 30 }, + { "DashGreen", 30, 40 }, + { "DashCyan", 40, 50 }, + { "DashBlue", 50, 60 }, + { "DashPurple", 60, 70 }, + { "DashPink", 70, 80 }, + }, + }, + }, + }, + { + type = "padding", + val = 2, + }, + { + type = "group", + val = { + { + type = "text", + val = function() return lazy.stats().loaded .. " plugin" .. (lazy.stats().loaded == 1 and "" or "s") .. " loaded" end, + opts = { + position = "center", + } + }, + { + type = "text", + val = "prismite | a theme made by delta___", + opts = { + position = "center", + hl = { + { "Comment", 0, 27 }, + { "DashEmphasis", 27, 35 } + } + } + } + }, + }, + }, +} diff --git a/.config/nvim/lua/icons.lua b/.config/nvim/lua/icons.lua new file mode 100644 index 0000000..e058e29 --- /dev/null +++ b/.config/nvim/lua/icons.lua @@ -0,0 +1,45 @@ +return { + diagnostics = { + Error = " ", + Warn = " ", + Hint = " ", + Info = " ", + }, + kinds = { + Array = " ", + Boolean = " ", + Class = " ", + Color = " ", + Constant = " ", + Constructor = " ", + Copilot = " ", + Enum = " ", + EnumMember = " ", + Event = " ", + Field = " ", + File = " ", + Folder = " ", + Function = " ", + Interface = " ", + Key = " ", + Keyword = " ", + Method = " ", + Module = " ", + Namespace = " ", + Null = " ", + Number = " ", + Object = " ", + Operator = " ", + Package = " ", + Property = " ", + Reference = " ", + Snippet = " ", + String = " ", + Struct = " ", + Text = " ", + TypeParameter = " ", + Unit = " ", + Value = " ", + Variable = " ", + }, +} diff --git a/.config/nvim/lua/local_plugins/color_converter/lua/color_converter.lua b/.config/nvim/lua/local_plugins/color_converter/lua/color_converter.lua new file mode 100644 index 0000000..ff8aa70 --- /dev/null +++ b/.config/nvim/lua/local_plugins/color_converter/lua/color_converter.lua @@ -0,0 +1,14 @@ +local menu = require "ui" + +return { + setup = function () + vim.api.nvim_create_user_command( + "ColorConverter", + function() + menu:mount() + end, + {} + ) + end, + config = function(opt) end +} diff --git a/.config/nvim/lua/local_plugins/color_converter/lua/ui.lua b/.config/nvim/lua/local_plugins/color_converter/lua/ui.lua new file mode 100644 index 0000000..a2bf115 --- /dev/null +++ b/.config/nvim/lua/local_plugins/color_converter/lua/ui.lua @@ -0,0 +1,47 @@ +local mcolors = require "mini.colors" +local Menu = require("nui.menu") +local event = require("nui.utils.autocmd").event + +local popup_options = { + relative = "cursor", + position = { + row = 2, + col = 1, + }, + border = { + style = "rounded", + text = { + top = "[Choose Item]", + top_align = "center", + }, + }, +} + +local menu = Menu(popup_options, { + lines = { + Menu.separator("Group One"), + Menu.item("Item 1"), + Menu.item("Item 2"), + Menu.separator("Group Two", { + char = "-", + text_align = "right", + }), + Menu.item("Item 3"), + Menu.item("Item 4"), + }, + -- max_width = 20, + keymap = { + focus_next = { "j", "<Down>", "<Tab>" }, + focus_prev = { "k", "<Up>", "<S-Tab>" }, + close = { "<Esc>", "<C-c>" }, + submit = { "<CR>", "<Space>" }, + }, + on_close = function() + print("CLOSED") + end, + on_submit = function(item) + print("SUBMITTED", vim.inspect(item)) + end, +}) + +return menu diff --git a/.config/nvim/lua/options.lua b/.config/nvim/lua/options.lua index 94ded94..6153825 100644 --- a/.config/nvim/lua/options.lua +++ b/.config/nvim/lua/options.lua @@ -1,23 +1,28 @@ local o = vim.o local opt = vim.opt -local wo = vim.wo -local bo = vim.bo --- Global options -- o.smarttab = true +o.exrc = true o.clipboard = "unnamedplus" -o.termguicolors = 24 +o.termguicolors = true o.list = true -opt.listchars = { space = "⋅", tab = " " } o.autochdir = true +o.wrap = false +o.number = true +o.relativenumber = true +o.expandtab = true +o.tabstop = 4 +o.smartindent = true +o.shiftwidth = 4 +o.sidescroll = 5 +o.timeout= false +o.scrolloff = 4 +o.sidescrolloff = 4 +o.sidescroll = 1 +o.cursorline = true +o.mouse = "" +o.fillchars = 'eob: ' +vim.g.mapleader = " " +vim.g.maplocalleader = vim.g.mapleader --- Window options -- -wo.number = true -wo.relativenumber = true - --- Buffer options -- -bo.expandtab = true -bo.tabstop = 4 -bo.smartindent = true ---bo.softtabstop = 0 -bo.shiftwidth = 2 +opt.listchars = { space = "⋅", tab = "--", precedes = "…", extends = "…" } diff --git a/.config/nvim/lua/plugins.lua b/.config/nvim/lua/plugins.lua deleted file mode 100644 index 7cc8321..0000000 --- a/.config/nvim/lua/plugins.lua +++ /dev/null @@ -1,83 +0,0 @@ -return { - { "echasnovski/mini.move", config = true }, - -- QoL - { "max397574/better-escape.nvim", config = true }, - - { "nvim-treesitter/nvim-treesitter", build = ":TSUpdate" }, - - -- UI - { - "lukas-reineke/indent-blankline.nvim", - config = true - }, - { - "folke/noice.nvim", - event = "VeryLazy", - opts = { - lsp = { - -- override markdown rendering so that **cmp** and other plugins use **Treesitter** - override = { - ["vim.lsp.util.convert_input_to_markdown_lines"] = true, - ["vim.lsp.util.stylize_markdown"] = true, - ["cmp.entry.get_documentation"] = true, - }, - }, - -- you can enable a preset for easier configuration - presets = { - -- bottom_search = true, -- use a classic bottom cmdline for search - command_palette = true, -- position the cmdline and popupmenu together - long_message_to_split = true, -- long messages will be sent to a split - inc_rename = false, -- enables an input dialog for inc-rename.nvim - lsp_doc_border = false, -- add a border to hover docs and signature help - }, - }, - dependencies = { - "MunifTanjim/nui.nvim", - }, - }, - { - "nvim-telescope/telescope.nvim", - config = true, - dependencies = { "nvim-lua/plenary.nvim" } - }, - { - "nvim-lualine/lualine.nvim", - opts = { - options = { - section_separators = { left = " ", right = " " }, - }, - sections = { - lualine_a = { "mode" }, - lualine_b = { "filename" }, - lualine_c = {}, - lualine_x = { "filetype" }, - lualine_y = { "progress" }, - lualine_z = { "location" }, - }, - }, - }, - { "stevearc/dressing.nvim", config = true }, - { - "folke/tokyonight.nvim", - lazy = false, - priority = 1000, - config = function() - vim.cmd [[colorscheme tokyonight-night]] - end, - }, - { - "nvim-neo-tree/neo-tree.nvim", - dependencies = { - "nvim-lua/plenary.nvim", - "nvim-tree/nvim-web-devicons", -- not strictly required, but recommended - "MunifTanjim/nui.nvim", - }, - }, - { - "goolord/alpha-nvim", - dependencies = { "nvim-tree/nvim-web-devicons" }, - config = function() - require("alpha").setup(require("alpha.themes.dashboard").config) - end, - }, -} diff --git a/.config/nvim/lua/plugins/coding.lua b/.config/nvim/lua/plugins/coding.lua new file mode 100644 index 0000000..fd636fa --- /dev/null +++ b/.config/nvim/lua/plugins/coding.lua @@ -0,0 +1,150 @@ +return { + -- { "echasnovski/mini.move", config = true }, + { "echasnovski/mini.comment", config = true }, + { "max397574/better-escape.nvim", config = true }, + { + "nvim-treesitter/nvim-treesitter", + build = ":TSUpdate", + config = function() + local configs = require "nvim-treesitter.configs" + configs.setup { + ensure_installed = { + "lua", + "c", + "vim", + "vimdoc", + "query", + "rust", + "fish", + "json", + "javascript", + "latex", + "markdown", + "markdown_inline", + "zig", + "typescript", + "toml", + "svelte", + "comment", + "html", + "typst", + "ron" + }, + highlight = { enable = true }, + indent = { enable = true }, + } + end, + }, + { + "ggandor/leap.nvim", + config = function() + local leap = require "leap" + leap.add_default_mappings() + leap.opts.highlight_unlabeled_phase_one_targets = true + end, + dependencies = { + "tpope/vim-repeat", + }, + }, + { + "hrsh7th/nvim-cmp", + opts = function() + vim.api.nvim_set_hl(0, "CmpGhostText", { link = "Comment", default = true }) + local cmp = require "cmp" + local defaults = require "cmp.config.default"() + return { + enabled = function() + local context = require "cmp.config.context" + if vim.api.nvim_get_mode().mode == "c" then + return true + else + return not context.in_treesitter_capture "comment" and not context.in_syntax_group "Comment" + end + end, + + completion = { + completeopt = "menu,menuone,noinsert", + }, + snippet = { + expand = function(args) + require("luasnip").lsp_expand(args.body) + end, + }, + window = { + completion = cmp.config.window.bordered(), + documentation = cmp.config.window.bordered(), + }, + mapping = cmp.mapping.preset.insert { + ["<C-j>"] = cmp.mapping.select_next_item { behavior = cmp.SelectBehavior.Insert }, + ["<C-k>"] = cmp.mapping.select_prev_item { behavior = cmp.SelectBehavior.Insert }, + ["<C-b>"] = cmp.mapping.scroll_docs(-4), + ["<C-f>"] = cmp.mapping.scroll_docs(4), + ["<S-CR>"] = cmp.mapping.abort(), + ["<CR>"] = cmp.mapping.confirm { select = false }, + }, + sources = cmp.config.sources { + { name = "nvim_lsp" }, + { name = "path" }, + }, + formatting = { + format = function(_, item) + local icons = require("icons").kinds + if icons[item.kind] then + item.kind = icons[item.kind] .. item.kind + end + return item + end, + }, + experimental = { + ghost_text = { + hl_group = "CmpGhostText", + }, + }, + sorting = defaults.sorting, + } + end, + dependencies = { + { "L3MON4D3/LuaSnip", build = "make install_jsregexp" }, + "hrsh7th/cmp-path", + "saadparwaiz1/cmp_luasnip", + }, + disabled = true + }, + { + "NvChad/nvim-colorizer.lua", + opts = { + user_default_options = { RGB = true, RRGGBB = true, RRGGBBAA = true, always_update = true, names = false }, + }, + }, + { + "echasnovski/mini.surround", + version = false, + opts = { + mappings = { + add = "\\a", -- Add surrounding in Normal and Visual modes + delete = "\\d", -- Delete surrounding + find = "\\f", -- Find surrounding (to the right) + find_left = "\\F", -- Find surrounding (to the left) + highlight = "\\h", -- Highlight surrounding + replace = "\\r", -- Replace surrounding + update_n_lines = "\\n", -- Update `n_lines` + } + } + }, + { + "color_converter", + dev = true, + dependencies = { + { 'echasnovski/mini.colors', version = '*' }, + "MunifTanjim/nui.nvim", + }, + -- lazy = false, + cmd = "ColorConverter", + config = true + }, + -- { + -- "m4xshen/hardtime.nvim", + -- config = true, + -- disabled = true + -- } +} diff --git a/.config/nvim/lua/plugins/colorscheme.lua b/.config/nvim/lua/plugins/colorscheme.lua new file mode 100644 index 0000000..e0f2a96 --- /dev/null +++ b/.config/nvim/lua/plugins/colorscheme.lua @@ -0,0 +1,57 @@ +return { + -- { + -- "folke/tokyonight.nvim", + -- lazy = false, + -- priority = 1000, + -- config = function() + -- vim.cmd [[colorscheme tokyonight-night]] + -- end, + -- }, + -- { + -- "chadcat7/prism", + -- config = function() + -- require("prism"):setup { + -- customSchemes = { + -- { + -- name = "prismite", + -- background = "#1b2026", + -- foreground = "#d9dfe4", + -- cursorline = "#1f242b", + -- comment = "#434754", + -- darker = "#161b22", + -- cursor = "#fdc267", + -- black = "#1b2026", -- useful when background is transparent + -- color0 = "#1f242b", + -- color1 = "#df625d", + -- color2 = "#91d89a", + -- color3 = "#fdc267", + -- color4 = "#8ec6ff", + -- color5 = "#f2b9c1", + -- color6 = "#77e2e3", + -- color7 = "#d9dfe4", + -- color8 = "#373D41", + -- color9 = "#f1726b", + -- color10 = "#a1e9aa", + -- color11 = "#ffd79d", + -- color12 = "#add6ff", + -- color13 = "#ffcbd2", + -- color14 = "#88f3f3", + -- color15 = "#d9dfe4", + -- }, + -- }, + -- currentTheme = "prismite", + -- reset = true, + -- reload = { "lualine" }, + -- } + -- end, + -- enabled = false + -- }, + { + dir = "/home/delta/Documents/RealProjects/lua/prismite.nvim", + lazy = false, + priority = 1000, + config = function() + vim.cmd [[colo prismite]] + end, + }, +} diff --git a/.config/nvim/lua/plugins/editor.lua b/.config/nvim/lua/plugins/editor.lua new file mode 100644 index 0000000..47fe8b4 --- /dev/null +++ b/.config/nvim/lua/plugins/editor.lua @@ -0,0 +1,24 @@ +return { + -- { "Saecki/crates.nvim", config = true }, + { + "nvim-telescope/telescope.nvim", + config = true, + dependencies = { "nvim-lua/plenary.nvim" }, + }, + { + "natecraddock/sessions.nvim", + config = true + }, + { + "f-person/git-blame.nvim", + event = "VeryLazy", + opts = { + date_format = "%r" + }, + }, + { + "chentoast/marks.nvim", + -- event = "VeryLazy", + config = true + } +} diff --git a/.config/nvim/lua/plugins/lsp.lua b/.config/nvim/lua/plugins/lsp.lua new file mode 100644 index 0000000..1063c5d --- /dev/null +++ b/.config/nvim/lua/plugins/lsp.lua @@ -0,0 +1,254 @@ +local icons = require("icons") + +-- this is horrible and it should be redone someday +-- but that day is not today + +return { + { + "neovim/nvim-lspconfig", + -- enabled = false, + event = { "BufReadPre", "BufNewFile" }, + dependencies = { + { "williamboman/mason.nvim", config = true, lazy = false }, + { "mason-org/mason-lspconfig.nvim", opts = { automatic_enable = false } }, + "hrsh7th/cmp-nvim-lsp", + }, + opts = { + diagnostics = { + }, + servers = { + lua_ls = { + settings = { + Lua = { + -- workspace = { + -- checkThirdParty = false, + -- library = { + -- vim.env.VIMRUNTIME, + -- }, + -- }, + completion = { + showWord = "Disable", + displayContext = 8, + }, + hint = { + enable = true, + }, + diagnostics = { "trailing-space" }, + }, + }, + }, + rust_analyzer = { settings = { completion = { fullFunctionSignatures = true } } }, + ts_ls = {}, + }, + setup = {} + }, + config = function(_, opts) + local function on_attach(fn) + vim.api.nvim_create_autocmd("LspAttach", { + callback = function(args) + local buffer = args.buf + local client = vim.lsp.get_client_by_id(args.data.client_id) + fn(client, buffer) + end, + }) + end + + ---@param method string + ---@param fn fun(client:vim.lsp.Client, buffer) + local function on_supports_method(method, fn) + -- cache[method] = cache[method] or setmetatable({}, { __mode = "k" }) + return vim.api.nvim_create_autocmd("User", { + pattern = "LspSupportsMethod", + callback = function(args) + local client = vim.lsp.get_client_by_id(args.data.client_id) + local buffer = args.data.buffer ---@type number + if client and method == args.data.method then + return fn(client, buffer) + end + end, + }) + end + + -- function _check_methods(client, buffer) + -- if + -- -- don't trigger on invalid buffers + -- not vim.api.nvim_buf_is_valid(buffer) or + -- -- don't trigger on non-listed buffers + -- vim.bo[buffer].buflisted or + -- -- don't trigger on nofile buffers + -- vim.bo[buffer].buftype == "nofile" + -- then + -- return + -- end + -- for method, clients in pairs(M._supports_method) do + -- clients[client] = clients[client] or {} + -- if not clients[client][buffer] then + -- if client.supports_method and client.supports_method(method, { bufnr = buffer }) then + -- clients[client][buffer] = true + -- vim.api.nvim_exec_autocmds("User", { + -- pattern = "LspSupportsMethod", + -- data = { client_id = client.id, buffer = buffer, method = method }, + -- }) + -- end + -- end + -- end + -- end + + on_supports_method("textDocument/inlayHint", function(client, buffer) + if + vim.api.nvim_buf_is_valid(buffer) + and vim.bo[buffer].buftype == "" + and not vim.tbl_contains(opts.inlay_hints.exclude, vim.bo[buffer].filetype) + then + vim.lsp.inlay_hint.enable(true, { bufnr = buffer }) + end + end) + + vim.diagnostic.config({ + underline = true, + update_in_insert = false, + virtual_text = { + spacing = 4, + source = "if_many", + prefix = function(diagnostic) + for name, icon in pairs(icons.diagnostics) do + if diagnostic.severity == vim.diagnostic.severity[name:upper()] then + return icon + end + end + end + + }, + severity_sort = true, + }) + + local servers = opts.servers + local cmp_nvim_lsp = require "cmp_nvim_lsp" + local capabilities = vim.tbl_deep_extend( + "force", + {}, + vim.lsp.protocol.make_client_capabilities(), + cmp_nvim_lsp.default_capabilities() + ) + + local function setup(server) + local server_opts = vim.tbl_deep_extend("force", { + capabilities = vim.deepcopy(capabilities), + }, servers[server] or {}) + + if opts.setup[server] then + if opts.setup[server](server, server_opts) then + return + end + elseif opts.setup["*"] then + if opts.setup["*"](server, server_opts) then + return + end + end + require("lspconfig")[server].setup(server_opts) + end + + local mlsp = require "mason-lspconfig" + local all_mslp_servers = require("mason-lspconfig").get_mappings().lspconfig_to_package + + local ensure_installed = {} + for server, server_opts in pairs(servers) do + if server_opts then + server_opts = server_opts == true and {} or server_opts + if server_opts.mason == false or not vim.tbl_contains(all_mslp_servers, server) then + setup(server) + else + ensure_installed[#ensure_installed + 1] = server + end + end + end + + -- mlsp.setup { ensure_installed = ensure_installed, handlers = { setup } } + mlsp.setup { ensure_installed = ensure_installed } + end, + -- config = function(_, opts) + -- local cache = {} + -- + -- + -- + -- + -- on_attach(function(client, buffer) + -- -- TODO: add keybinds + -- end) + -- + -- local register_capability = vim.lsp.handlers["client/registerCapability"] + -- + -- vim.lsp.handlers["client/registerCapability"] = function(err, res, ctx) + -- local ret = register_capability(err, res, ctx) + -- local client_id = ctx.client_id + -- local client = vim.lsp.get_client_by_id(client_id) + -- local buffer = vim.api.nvim_get_current_buf() + -- -- TODO: add keybinds + -- return ret + -- end + -- + -- for name, icon in pairs(require("icons").diagnostics) do + -- name = "DiagnosticSign" .. name + -- vim.fn.sign_define(name, { text = icon, texthl = name, numhl = "" }) + -- end + -- + -- local inlay_hint = vim.lsp.buf.inlay_hint or vim.lsp.inlay_hint + -- + -- + -- if type(opts.diagnostics.virtual_text) == "table" and opts.diagnostics.virtual_text.prefix == "icons" then + -- opts.diagnostics.virtual_text.prefix = vim.fn.has "nvim-0.10.0" == 0 and "●" + -- end + -- +-- ) + -- + -- local servers = opts.servers + -- local has_cmp, cmp_nvim_lsp = pcall(require, "cmp_nvim_lsp") + -- local capabilities = vim.tbl_deep_extend( + -- "force", + -- {}, + -- vim.lsp.protocol.make_client_capabilities(), + -- has_cmp and cmp_nvim_lsp.default_capabilities() or {}, + -- opts.capabilities or {} + -- ) + -- + -- local function setup(server) + -- local server_opts = vim.tbl_deep_extend("force", { + -- capabilities = vim.deepcopy(capabilities), + -- }, servers[server] or {}) + -- + -- if opts.setup[server] then + -- if opts.setup[server](server, server_opts) then + -- return + -- end + -- elseif opts.setup["*"] then + -- if opts.setup["*"](server, server_opts) then + -- return + -- end + -- end + -- require("lspconfig")[server].setup(server_opts) + -- end + -- + -- local have_mason, mlsp = pcall(require, "mason-lspconfig") + -- local all_mslp_servers = {} + -- if have_mason then + -- all_mslp_servers = vim.tbl_keys(require("mason-lspconfig.mappings.server").lspconfig_to_package) + -- end + -- + -- local ensure_installed = {} + -- for server, server_opts in pairs(servers) do + -- if server_opts then + -- server_opts = server_opts == true and {} or server_opts + -- if server_opts.mason == false or not vim.tbl_contains(all_mslp_servers, server) then + -- setup(server) + -- else + -- ensure_installed[#ensure_installed + 1] = server + -- end + -- end + -- end + -- + -- if have_mason then + -- mlsp.setup { ensure_installed = ensure_installed, handlers = { setup } } + -- end + -- end, + }, +} diff --git a/.config/nvim/lua/plugins/ui.lua b/.config/nvim/lua/plugins/ui.lua new file mode 100644 index 0000000..e79ee3a --- /dev/null +++ b/.config/nvim/lua/plugins/ui.lua @@ -0,0 +1,477 @@ +local leap_active = false +vim.api.nvim_create_autocmd("User", { + pattern = "LeapEnter", + callback = function() + leap_active = true + end, +}) + +vim.api.nvim_create_autocmd("User", { + pattern = "LeapLeave", + callback = function() + leap_active = false + end, +}) + +local nvim_tree_root = nil + +return { + { + "willothy/nvim-cokeline", + + config = function() + local hl = require("cokeline.hlgroups") + local is_picking_focus = require('cokeline.mappings').is_picking_focus + local is_picking_close = require('cokeline.mappings').is_picking_close + + local function has_diagnostics(buffer) + return buffer.diagnostics.errors > 0 or buffer.diagnostics.warnings > 0 or buffer.diagnostics.infos > 0 or buffer.diagnostics.hints > 0 + end + + require("cokeline").setup { + show_if_buffers_are_at_least = 0, + components = { + { + text = function(buffer) + return buffer.is_first and (require "cokeline.sidebar".get_width("left") == 0 and "" or "│") or "▎" + end, + fg = function() + return hl.get_hl_attr("WinSeparator", "fg") + end, + }, + { + text = function (buffer) + return (is_picking_focus() or is_picking_close()) and " " .. buffer.pick_letter or "" + end, + fg = function () + return hl.get_hl_attr("PrismiteYellow", "fg") + end + }, + { + text = function(buffer) + return " " .. buffer.devicon.icon + end, + fg = function(buffer) + return buffer.devicon.color + end, + }, + { + text = function(buffer) + return buffer.unique_prefix + end, + fg = function() + return hl.get_hl_attr("Comment", "fg") + end, + italic = true, + }, + { + text = function(buffer) + return buffer.filename + end, + }, + { + ---@param buffer Buffer + text = function(buffer) + return buffer.is_modified and " " or "" + end, + }, + { + text = function(buffer) + return has_diagnostics(buffer) and " [" or "" + end, + fg = function() + return hl.get_hl_attr("WinSeparator", "fg") + end, + }, + { + text = function(buffer) + local errors = buffer.diagnostics.errors + return errors > 0 and errors or "" + end, + fg = function () + return hl.get_hl_attr("DiagnosticError", "fg") + + end + }, + { + text = function (buffer) + local bd = buffer.diagnostics + return (bd.errors > 0 and (bd.warnings > 0 or bd.infos > 0 or bd.hints > 0)) and "|" or "" + end, + fg = function() + return hl.get_hl_attr("WinSeparator", "fg") + end, + }, + { + text = function(buffer) + local warnings = buffer.diagnostics.warnings + return warnings > 0 and warnings or "" + end, + fg = function () + return hl.get_hl_attr("DiagnosticWarn", "fg") + end + }, + { + text = function (buffer) + local bd = buffer.diagnostics + return (bd.warnings > 0 and (bd.infos > 0 or bd.hints > 0)) and "|" or "" + end, + fg = function() + return hl.get_hl_attr("WinSeparator", "fg") + end, + }, + { + text = function(buffer) + local infos = buffer.diagnostics.infos + return infos > 0 and infos or "" + end, + fg = function () + return hl.get_hl_attr("DiagnosticInfo", "fg") + end + }, + { + text = function (buffer) + local bd = buffer.diagnostics + return (bd.infos > 0 and bd.hints > 0) and "|" or "" + end, + fg = function() + return hl.get_hl_attr("WinSeparator", "fg") + end, + }, + { + text = function(buffer) + local hints = buffer.diagnostics.hints + return hints > 0 and hints or "" + end, + fg = function () + return hl.get_hl_attr("DiagnosticHint", "fg") + end + }, + { + text = function(buffer) + return has_diagnostics(buffer) and "]" or "" + end, + fg = function() + return hl.get_hl_attr("WinSeparator", "fg") + end, + }, + { + text = " " + } + }, + buffers = { + delete_on_right_click = false + }, + mappings = { + disable_mouse = true + }, + sidebar = { + components = { + { + text = function() + local width = require("cokeline.sidebar").get_width("left") + if #nvim_tree_root > width then + return nvim_tree_root:sub(1, width - 2) .. "…" + else + return nvim_tree_root + end + end, + bg = function() + return hl.get_hl_attr("TabLineSel", "bg") + end, + }, + { + text = function() + return string.rep( + " ", + math.max(0, require("cokeline.sidebar").get_width("left") - #nvim_tree_root) + ) + end, + bg = function() + return hl.get_hl_attr("TabLineSel", "bg") + end + }, + } + } + } + end, + dependencies = { "nvim-tree/nvim-web-devicons", "nvim-lua/plenary.nvim" }, + }, + { + "folke/trouble.nvim", + opts = {}, + dependencies = { "nvim-tree/nvim-web-devicons" }, + }, + { + "nvimdev/indentmini.nvim", + config = true, + opts = { + char = "▏" + } + }, + { + "folke/noice.nvim", + event = "VeryLazy", + opts = { + cmdline = { + format = { + cmdline = { icon = ":" }, + }, + }, + lsp = { + override = { + ["vim.lsp.util.convert_input_to_markdown_lines"] = true, + ["vim.lsp.util.stylize_markdown"] = true, + ["cmp.entry.get_documentation"] = true, + }, + }, + presets = { + command_palette = true, + long_message_to_split = true, + inc_rename = false, + lsp_doc_border = true, + }, + messages = { + view_search = false, + }, + routes = { + { + filter = { + event = "msg_show", + kind = "", + find = "Already at newest change", + }, + view = "mini", + }, + { + filter = { + event = "msg_show", + kind = "", + find = "Already at oldest change", + }, + view = "mini", + }, + { + filter = { + event = "msg_show", + kind = "", + find = "E486: Pattern not found:" + }, + view = "mini" + }, + -- normal search + { + filter = { + event = "msg_show", + kind = "", + find = "E486: Pattern not found:" + }, + view = "mini" + }, + -- failed search + { + filter = { + event = "msg_show", + kind = "", + find = "E486: Pattern not found:" + }, + view = "mini" + }, + -- undo + { + filter = { + event = "msg_show", + kind = "", + find = "%d+ .+ line.?; before #%d+", + }, + view = "mini", + }, + { + filter = { + event = "msg_show", + kind = "", + find = "%d+ .+ line.?; before #%d+", + }, + view = "mini", + }, + -- redo + { + filter = { + event = "msg_show", + kind = "", + find = "%d+ change; after #%d+", + }, + view = "mini" + }, + }, + }, + dependencies = { + "MunifTanjim/nui.nvim", + "rcarriga/nvim-notify", + }, + }, + { + "nvim-lualine/lualine.nvim", + event = "VeryLazy", + opts = { + options = { + section_separators = { left = "", right = "" }, + component_separators = { left = "", right = "" }, + fmt = function(str) + return vim.trim(str) + end, + globalstatus = true, + disabled_filetypes = { "NvimTree" }, + }, + sections = { + lualine_a = { + "mode", + { + function() + return "[L]" + end, + cond = function () + return leap_active + end + }, + { + function() + local reg = vim.fn.reg_recording() + if reg == "" then return "" end -- not recording + return "[@" .. reg .. "]" + end + } + }, + lualine_b = { "filename" }, + lualine_c = { "diagnostics", "diff" }, + lualine_x = { + { + function () + local noice_loaded, noice = pcall(require, "noice") + if noice_loaded then + return noice.api.status.command.get() + else + return "" + end + end, + cond = function() + local noice_loaded, noice = pcall(require, "noice") + if noice_loaded then + return noice.api.status.command.has() + else + return false + end + end + } + }, + lualine_y = { "filetype" }, + lualine_z = { "searchcount", "progress", "location" }, + }, + }, + dependencies = { + "nvim-tree/nvim-web-devicons", + }, + }, + { "stevearc/dressing.nvim", config = true }, + { + "nvim-tree/nvim-tree.lua", + opts = { + sort_by = "case_sensitive", + view = { + width = 30, + }, + renderer = { + root_folder_label = function() return ".." end, + indent_markers = { + enable = true, + }, + icons = { + show = { + folder_arrow = false, + }, + glyphs = { + git = { + unstaged = "-", + staged = "+", + unmerged = "", + renamed = "", + untracked = "", + deleted = "", + ignored = "", + }, + }, + }, + }, + filters = { + git_ignored = false, + custom = { + "^.git$", + }, + }, + }, + config = function(_, opts) + local api = require("nvim-tree.api") + local Event = api.events.Event + + api.events.subscribe(Event.Ready, function() + nvim_tree_root = api.tree.get_nodes().absolute_path + end) + + api.events.subscribe(Event.TreeRendered, function() + nvim_tree_root = api.tree.get_nodes().absolute_path + end) + + require("nvim-tree").setup(vim.tbl_extend("force", opts, { + -- on_attach = function () + -- local function opts(desc) + -- return { desc = "nvim-tree: " .. desc, buffer = bufnr, noremap = true, silent = true, nowait = true } + -- end + -- + -- local function edit_or_open() + -- local node = api.tree.get_node_under_cursor() + -- + -- if node.nodes ~= nil then + -- -- expand or collapse folder + -- api.node.open.edit() + -- else + -- -- open file + -- api.node.open.edit() + -- -- Close the tree if file was opened + -- api.tree.close() + -- end + -- end + -- + -- -- open as vsplit on current node + -- local function vsplit_preview() + -- local node = api.tree.get_node_under_cursor() + -- + -- if node.nodes ~= nil then + -- -- expand or collapse folder + -- api.node.open.edit() + -- else + -- -- open file as vsplit + -- api.node.open.vertical() + -- end + -- + -- -- Finally refocus on tree if it was lost + -- api.tree.focus() + -- end + -- + -- vim.keymap.set("n", "l", edit_or_open, opts("Edit Or Open")) + -- vim.keymap.set("n", "h", api.tree.close, opts("Close")) + -- end + -- + })) + end, + dependencies = { + "nvim-tree/nvim-web-devicons", + }, + }, + { + "goolord/alpha-nvim", + dependencies = { "nvim-tree/nvim-web-devicons" }, + config = function() + require("alpha").setup(require "dash") + end, + }, +} diff --git a/.config/nvim/lua/plugins/utils.lua b/.config/nvim/lua/plugins/utils.lua new file mode 100644 index 0000000..3c16433 --- /dev/null +++ b/.config/nvim/lua/plugins/utils.lua @@ -0,0 +1,4 @@ +return { + { 'echasnovski/mini.colors', version = '*' }, + "MunifTanjim/nui.nvim", +} diff --git a/.config/rmpc/config.ron b/.config/rmpc/config.ron new file mode 100644 index 0000000..7822c58 --- /dev/null +++ b/.config/rmpc/config.ron @@ -0,0 +1,167 @@ +#![enable(implicit_some)] +#![enable(unwrap_newtypes)] +#![enable(unwrap_variant_newtypes)] +( + address: "127.0.0.1:6600", + password: None, + theme: "prismite", + cache_dir: None, + on_song_change: None, + volume_step: 5, + max_fps: 30, + scrolloff: 0, + wrap_navigation: false, + enable_mouse: true, + enable_config_hot_reload: true, + status_update_interval_ms: 1000, + rewind_to_start_sec: None, + reflect_changes_to_playlist: false, + select_current_song_on_change: false, + browser_song_sort: [Disc, Track, Artist, Title], + directories_sort: SortFormat(group_by_type: true, reverse: false), + album_art: ( + method: Auto, + max_size_px: (width: 1200, height: 1200), + disabled_protocols: ["http://", "https://"], + vertical_align: Center, + horizontal_align: Center, + ), + keybinds: ( + global: { + ":": CommandMode, + ",": VolumeDown, + "s": Stop, + ".": VolumeUp, + "<Tab>": NextTab, + "<S-Tab>": PreviousTab, + "1": SwitchToTab("Queue"), + "2": SwitchToTab("Directories"), + "3": SwitchToTab("Artists"), + "4": SwitchToTab("Album Artists"), + "5": SwitchToTab("Albums"), + "6": SwitchToTab("Playlists"), + "7": SwitchToTab("Search"), + "q": Quit, + ">": NextTrack, + "p": TogglePause, + "<": PreviousTrack, + "f": SeekForward, + "z": ToggleRepeat, + "x": ToggleRandom, + "c": ToggleConsume, + "v": ToggleSingle, + "b": SeekBack, + "~": ShowHelp, + "u": Update, + "U": Rescan, + "I": ShowCurrentSongInfo, + "O": ShowOutputs, + "P": ShowDecoders, + "R": AddRandom, + }, + navigation: { + "k": Up, + "j": Down, + "h": Left, + "l": Right, + "<Up>": Up, + "<Down>": Down, + "<Left>": Left, + "<Right>": Right, + "<C-k>": PaneUp, + "<C-j>": PaneDown, + "<C-h>": PaneLeft, + "<C-l>": PaneRight, + "<C-u>": UpHalf, + "N": PreviousResult, + "a": Add, + "A": AddAll, + "r": Rename, + "n": NextResult, + "g": Top, + "<Space>": Select, + "<C-Space>": InvertSelection, + "G": Bottom, + "<CR>": Confirm, + "i": FocusInput, + "J": MoveDown, + "<C-d>": DownHalf, + "/": EnterSearch, + "<C-c>": Close, + "<Esc>": Close, + "K": MoveUp, + "D": Delete, + "B": ShowInfo, + }, + queue: { + "D": DeleteAll, + "<CR>": Play, + "<C-s>": Save, + "a": AddToPlaylist, + "d": Delete, + "C": JumpToCurrent, + "X": Shuffle, + }, + ), + search: ( + case_sensitive: false, + mode: Contains, + tags: [ + (value: "any", label: "Any Tag"), + (value: "artist", label: "Artist"), + (value: "album", label: "Album"), + (value: "albumartist", label: "Album Artist"), + (value: "title", label: "Title"), + (value: "filename", label: "Filename"), + (value: "genre", label: "Genre"), + ], + ), + artists: ( + album_display_mode: SplitByDate, + album_sort_by: Date, + ), + tabs: [ + ( + name: "Queue", + pane: Split( + direction: Horizontal, + panes: [ + ( + size: "40%", + pane: Pane(AlbumArt) + ), + ( + size: "60%", + pane: Pane(Queue), + borders: "LEFT" + ) + ], + ), + ), + ( + name: "Directories", + pane: Pane(Directories), + ), + ( + name: "Artists", + pane: Pane(Artists), + ), + ( + name: "Album Artists", + pane: Pane(AlbumArtists), + ), + ( + name: "Albums", + pane: Pane(Albums), + ), + ( + name: "Playlists", + pane: Pane(Playlists), + ), + ( + name: "Search", + pane: Pane(Search), + ), + ], +) + diff --git a/.config/rmpc/themes/prismite.ron b/.config/rmpc/themes/prismite.ron new file mode 100644 index 0000000..88fbc4b --- /dev/null +++ b/.config/rmpc/themes/prismite.ron @@ -0,0 +1,195 @@ +#![enable(implicit_some)] +#![enable(unwrap_newtypes)] +#![enable(unwrap_variant_newtypes)] +( + default_album_art_path: None, + show_song_table_header: true, + draw_borders: true, + format_tag_separator: " | ", + browser_column_widths: [20, 38, 42], + background_color: None, + text_color: None, + header_background_color: None, + modal_background_color: None, + modal_backdrop: false, + preview_label_style: (fg: "yellow"), + preview_metadata_group_style: (fg: "yellow", modifiers: "Bold"), + tab_bar: ( + enabled: true, + active_style: (fg: "#12161c", bg: "yellow", modifiers: "Bold"), + inactive_style: (), + ), + highlighted_item_style: (fg: "yellow", modifiers: "Bold"), + current_item_style: (fg: "gray", bg: "#171c22"), + borders_style: (fg: "#323b46"), + highlight_border_style: (fg: "#323b46"), + symbols: ( + song: "", + dir: "", + playlist: "", + marker: "+", + ellipsis: "...", + song_style: None, + dir_style: None, + playlist_style: None, + ), + level_styles: ( + info: (fg: "yellow"), + warn: (fg: "#ff9f6f"), + error: (fg: "red"), + debug: (fg: "light_green"), + trace: (fg: "magenta"), + ), + progress_bar: ( + symbols: ["<", "=", ">", "=", ">"], + track_style: (fg: "#070c11"), + elapsed_style: (fg: "yellow"), + thumb_style: (fg: "yellow"), + ), + scrollbar: ( + symbols: ["│", "┃", "∧", "∨"], + track_style: (fg: "#070c11"), + ends_style: (fg: "#a8afb7"), + thumb_style: (fg: "yellow"), + ), + song_table_format: [ + ( + prop: (kind: Property(Artist), + default: (kind: Text("Unknown")) + ), + width: "20%", + ), + ( + prop: (kind: Property(Title), + default: (kind: Text("Unknown")) + ), + width: "35%", + ), + ( + prop: (kind: Property(Album), style: (fg: "gray"), + default: (kind: Text("Unknown Album"), style: (fg: "gray")) + ), + width: "30%", + ), + ( + prop: (kind: Property(Duration), + default: (kind: Text("-")) + ), + width: "15%", + alignment: Right, + ), + ], + components: {}, + layout: Split( + direction: Vertical, + panes: [ + ( + pane: Pane(Header), + size: "1", + ), + ( + pane: Pane(Tabs), + size: "3", + ), + ( + pane: Pane(TabContent), + size: "100%", + ), + ( + pane: Split( + direction: Horizontal, + panes: [ + ( + pane: Pane( + Property(content: [ + (kind: Property( + Status( + StateV2( + playing_label: "", + paused_label: "", + stopped_label: "" + ) + ) + )) + ]) + ), + size: "3" + ), + ( + pane: Pane( + Property(content: [ + (kind: Property(Status(Elapsed))), + (kind: Text(" / ")), + (kind: Property(Status(Duration))), + (kind: Text(" (")), + (kind: Property(Status(Bitrate))), + (kind: Text(" kbps)")) + ]) + ), + size: "20%", + ), + ( + pane: Pane(ProgressBar), + size: "100%" + ), + ] + ), + size: "2", + borders: "TOP" + ), + ], + ), + header: ( + rows: [ + ( + left: [ + (kind: Property(Widget(ScanStatus))), + (kind: Property(Widget(Volume))), + ], + center: [ + (kind: Property(Song(Artist)), style: (fg: "yellow", modifiers: "Bold"), + default: (kind: Text("Unknown"), style: (fg: "yellow", modifiers: "Bold")) + ), + (kind: Text(" - ")), + (kind: Property(Song(Album)), + default: (kind: Text("Unknown Album")) + ), + (kind: Text(" | ")), + (kind: Property(Song(Title)), style: (modifiers: "Bold"), + default: (kind: Text("No Song"), style: (modifiers: "Bold")) + ) + ], + right: [ + (kind: Text("[ ")), + ( + kind: Property(Widget(States( + active_style: (fg: "yellow", modifiers: "Bold"), + separator_style: (fg: "gray"))) + ), + style: (fg: "gray") + ), + (kind: Text(" ]")), + ] + ), + ], + ), + browser_song_format: [ + ( + kind: Group([ + (kind: Property(Track)), + (kind: Text(" ")), + ]) + ), + ( + kind: Group([ + (kind: Property(Artist)), + (kind: Text(" - ")), + (kind: Property(Title)), + ]), + default: (kind: Property(Filename)) + ), + ], + lyrics: ( + timestamp: false + ) +) diff --git a/.config/wezterm/.nvim.lua b/.config/wezterm/.nvim.lua new file mode 100644 index 0000000..9c7049d --- /dev/null +++ b/.config/wezterm/.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/wezterm/appearance.lua b/.config/wezterm/appearance.lua index 2376174..f786e85 100644 --- a/.config/wezterm/appearance.lua +++ b/.config/wezterm/appearance.lua @@ -1,62 +1,51 @@ -local wezterm = require "wezterm" +local wezterm = require("wezterm") local function basename(s) - return string.gsub(s, '(.*[/\\])(.*)', '%2') + return string.gsub(s, "(.*[/\\])(.*)", "%2") end local function home_or_path(path) - local realpath = path:sub(14, -1) -- file://lambda - local home = os.getenv("HOME") - return realpath == home and "~" or realpath + local realpath = tostring(path):sub(14, -1) -- file://lambda + local home = os.getenv("HOME") + return realpath == home and "~" or realpath end wezterm.on("format-tab-title", function(tab) - local tab_format = { - { Text = " " }, - { Text = tostring(tab.tab_index + 1) }, - { Text = " " }, - { Text = basename(home_or_path(tab.active_pane.current_working_dir)) }, - { Text = " " }, - "ResetAttributes", - } - - if tab.is_active then - table.insert(tab_format, 1, { Background = { Color = C.black } }) - end - - return wezterm.format(tab_format) + local tab_format = { + { Text = " " }, + { Text = tostring(tab.tab_index + 1) }, + { Text = " " }, + { Text = basename(home_or_path(tab.active_pane.current_working_dir)) }, + { Text = " " }, + "ResetAttributes", + } + + return wezterm.format(tab_format) end) return { - -- tabs - show_new_tab_button_in_tab_bar = false, - use_fancy_tab_bar = false, - tab_bar_at_bottom = true, - hide_tab_bar_if_only_one_tab = true, - - -- font - font = wezterm.font { - family = "FiraCode Nerd Font Mono", - harfbuzz_features = { "ss02", "ss03", "ss04", "ss08" } - }, - font_size = 10, - adjust_window_size_when_changing_font_size = false, - - -- cursor - default_cursor_style = "BlinkingUnderline", - cursor_blink_rate = 500, - animation_fps = 1, - - -- window - window_padding = { - left = 10, - right = 10, - top = 10, - bottom = 10 - }, - - -- colors - colors = R "colors", - bold_brightens_ansi_colors = false, - + -- tabs + show_new_tab_button_in_tab_bar = false, + use_fancy_tab_bar = false, + tab_bar_at_bottom = true, + hide_tab_bar_if_only_one_tab = true, + + -- font + font = wezterm.font({ + -- family = "FiraCode Nerd Font Mono", + family = "Iosevka Comfy", + -- harfbuzz_features = { "ss02", "ss03", "ss04", "ss08" } + }), + font_size = 10.5, + adjust_window_size_when_changing_font_size = false, + allow_square_glyphs_to_overflow_width = "Never", + + -- cursor + default_cursor_style = "BlinkingUnderline", + cursor_blink_rate = 500, + animation_fps = 1, + + -- colors + colors = R("colors"), + bold_brightens_ansi_colors = false, } diff --git a/.config/wezterm/colors.lua b/.config/wezterm/colors.lua index 58ae639..e290a12 100644 --- a/.config/wezterm/colors.lua +++ b/.config/wezterm/colors.lua @@ -1,48 +1,48 @@ return { - foreground = C.fg, - background = C.bg, + foreground = C.fg(), + background = C.bg(), - cursor_bg = C.fg, - cursor_fg = C.bg, - cursor_border = C.bg, + cursor_bg = C.fg(), + cursor_fg = C.bg(), + cursor_border = C.bg(), - selection_bg = C.bright.black, - selection_fg = C.fg, + selection_bg = C.bg.high, + selection_fg = C.fg(), - visual_bell = C.white, + visual_bell = C.fg(), - ansi = { - C.black, - C.red, - C.green, - C.yellow, - C.blue, - C.pink, - C.cyan, - C.white, - }, - brights = { - C.bright.black, - C.bright.red, - C.bright.green, - C.bright.yellow, - C.bright.blue, - C.bright.pink, - C.bright.cyan, - C.bright.white, - }, + ansi = { + C.bg.lowest, + C.red(), + C.green(), + C.yellow(), + C.blue(), + C.pink(), + C.cyan(), + C.fg(), + }, + brights = { + C.bg.low, + C.red.bright, + C.green.bright, + C.yellow.bright, + C.blue.bright, + C.pink.bright, + C.cyan.bright, + C.fg.high, + }, - tab_bar = { - background = C.bg, + tab_bar = { + background = C.bg(), - active_tab = { - bg_color = C.bg, - fg_color = C.fg - }, + active_tab = { + bg_color = C.bg.high, + fg_color = C.fg(), + }, - inactive_tab = { - bg_color = C.bg, - fg_color = C.fg_dark - }, - } + inactive_tab = { + bg_color = C.bg(), + fg_color = C.fg.low, + }, + }, } diff --git a/.config/wezterm/inspect.lua b/.config/wezterm/inspect.lua index ce90145..f744939 100644 --- a/.config/wezterm/inspect.lua +++ b/.config/wezterm/inspect.lua @@ -1,25 +1,18 @@ -local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local math = _tl_compat and _tl_compat.math or math; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table -local inspect = {Options = {}, } - - - - - - - - - - - - - - - - - -inspect._VERSION = 'inspect.lua 3.1.0' -inspect._URL = 'http://github.com/kikito/inspect.lua' -inspect._DESCRIPTION = 'human-readable representations of tables' +local _tl_compat +if (tonumber((_VERSION or ""):match("[%d.]*$")) or 0) < 5.3 then + local p, m = pcall(require, "compat53.module") + if p then + _tl_compat = m + end +end +local math = _tl_compat and _tl_compat.math or math +local string = _tl_compat and _tl_compat.string or string +local table = _tl_compat and _tl_compat.table or table +local inspect = { Options = {} } + +inspect._VERSION = "inspect.lua 3.1.0" +inspect._URL = "http://github.com/kikito/inspect.lua" +inspect._DESCRIPTION = "human-readable representations of tables" inspect._LICENSE = [[ MIT LICENSE @@ -44,8 +37,16 @@ inspect._LICENSE = [[ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]] -inspect.KEY = setmetatable({}, { __tostring = function() return 'inspect.KEY' end }) -inspect.METATABLE = setmetatable({}, { __tostring = function() return 'inspect.METATABLE' end }) +inspect.KEY = setmetatable({}, { + __tostring = function() + return "inspect.KEY" + end, +}) +inspect.METATABLE = setmetatable({}, { + __tostring = function() + return "inspect.METATABLE" + end, +}) local tostring = tostring local rep = string.rep @@ -56,317 +57,310 @@ local fmt = string.format local _rawget if rawget then - _rawget = rawget + _rawget = rawget else - _rawget = function(t, k) return t[k] end + _rawget = function(t, k) + return t[k] + end end local function rawpairs(t) - return next, t, nil + return next, t, nil end - - local function smartQuote(str) - if match(str, '"') and not match(str, "'") then - return "'" .. str .. "'" - end - return '"' .. gsub(str, '"', '\\"') .. '"' + if match(str, '"') and not match(str, "'") then + return "'" .. str .. "'" + end + return '"' .. gsub(str, '"', '\\"') .. '"' end - local shortControlCharEscapes = { - ["\a"] = "\\a", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n", - ["\r"] = "\\r", ["\t"] = "\\t", ["\v"] = "\\v", ["\127"] = "\\127", + ["\a"] = "\\a", + ["\b"] = "\\b", + ["\f"] = "\\f", + ["\n"] = "\\n", + ["\r"] = "\\r", + ["\t"] = "\\t", + ["\v"] = "\\v", + ["\127"] = "\\127", } local longControlCharEscapes = { ["\127"] = "\127" } for i = 0, 31 do - local ch = char(i) - if not shortControlCharEscapes[ch] then - shortControlCharEscapes[ch] = "\\" .. i - longControlCharEscapes[ch] = fmt("\\%03d", i) - end + local ch = char(i) + if not shortControlCharEscapes[ch] then + shortControlCharEscapes[ch] = "\\" .. i + longControlCharEscapes[ch] = fmt("\\%03d", i) + end end local function escape(str) - return (gsub(gsub(gsub(str, "\\", "\\\\"), - "(%c)%f[0-9]", longControlCharEscapes), - "%c", shortControlCharEscapes)) + return (gsub(gsub(gsub(str, "\\", "\\\\"), "(%c)%f[0-9]", longControlCharEscapes), "%c", shortControlCharEscapes)) end local luaKeywords = { - ['and'] = true, - ['break'] = true, - ['do'] = true, - ['else'] = true, - ['elseif'] = true, - ['end'] = true, - ['false'] = true, - ['for'] = true, - ['function'] = true, - ['goto'] = true, - ['if'] = true, - ['in'] = true, - ['local'] = true, - ['nil'] = true, - ['not'] = true, - ['or'] = true, - ['repeat'] = true, - ['return'] = true, - ['then'] = true, - ['true'] = true, - ['until'] = true, - ['while'] = true, + ["and"] = true, + ["break"] = true, + ["do"] = true, + ["else"] = true, + ["elseif"] = true, + ["end"] = true, + ["false"] = true, + ["for"] = true, + ["function"] = true, + ["goto"] = true, + ["if"] = true, + ["in"] = true, + ["local"] = true, + ["nil"] = true, + ["not"] = true, + ["or"] = true, + ["repeat"] = true, + ["return"] = true, + ["then"] = true, + ["true"] = true, + ["until"] = true, + ["while"] = true, } local function isIdentifier(str) - return type(str) == "string" and - not not str:match("^[_%a][_%a%d]*$") and - not luaKeywords[str] + return type(str) == "string" and not not str:match("^[_%a][_%a%d]*$") and not luaKeywords[str] end local flr = math.floor local function isSequenceKey(k, sequenceLength) - return type(k) == "number" and - flr(k) == k and - 1 <= (k) and - k <= sequenceLength + return type(k) == "number" and flr(k) == k and 1 <= k and k <= sequenceLength end local defaultTypeOrders = { - ['number'] = 1, ['boolean'] = 2, ['string'] = 3, ['table'] = 4, - ['function'] = 5, ['userdata'] = 6, ['thread'] = 7, + ["number"] = 1, + ["boolean"] = 2, + ["string"] = 3, + ["table"] = 4, + ["function"] = 5, + ["userdata"] = 6, + ["thread"] = 7, } local function sortKeys(a, b) - local ta, tb = type(a), type(b) - + local ta, tb = type(a), type(b) - if ta == tb and (ta == 'string' or ta == 'number') then - return (a) < (b) - end + if ta == tb and (ta == "string" or ta == "number") then + return a < b + end - local dta = defaultTypeOrders[ta] or 100 - local dtb = defaultTypeOrders[tb] or 100 + local dta = defaultTypeOrders[ta] or 100 + local dtb = defaultTypeOrders[tb] or 100 - - return dta == dtb and ta < tb or dta < dtb + return dta == dtb and ta < tb or dta < dtb end local function getKeys(t) - - local seqLen = 1 - while _rawget(t, seqLen) ~= nil do - seqLen = seqLen + 1 - end - seqLen = seqLen - 1 - - local keys, keysLen = {}, 0 - for k in rawpairs(t) do - if not isSequenceKey(k, seqLen) then - keysLen = keysLen + 1 - keys[keysLen] = k - end - end - table.sort(keys, sortKeys) - return keys, keysLen, seqLen + local seqLen = 1 + while _rawget(t, seqLen) ~= nil do + seqLen = seqLen + 1 + end + seqLen = seqLen - 1 + + local keys, keysLen = {}, 0 + for k in rawpairs(t) do + if not isSequenceKey(k, seqLen) then + keysLen = keysLen + 1 + keys[keysLen] = k + end + end + table.sort(keys, sortKeys) + return keys, keysLen, seqLen end local function countCycles(x, cycles) - if type(x) == "table" then - if cycles[x] then - cycles[x] = cycles[x] + 1 - else - cycles[x] = 1 - for k, v in rawpairs(x) do - countCycles(k, cycles) - countCycles(v, cycles) - end - countCycles(getmetatable(x), cycles) - end - end + if type(x) == "table" then + if cycles[x] then + cycles[x] = cycles[x] + 1 + else + cycles[x] = 1 + for k, v in rawpairs(x) do + countCycles(k, cycles) + countCycles(v, cycles) + end + countCycles(getmetatable(x), cycles) + end + end end local function makePath(path, a, b) - local newPath = {} - local len = #path - for i = 1, len do newPath[i] = path[i] end + local newPath = {} + local len = #path + for i = 1, len do + newPath[i] = path[i] + end - newPath[len + 1] = a - newPath[len + 2] = b + newPath[len + 1] = a + newPath[len + 2] = b - return newPath + return newPath end - -local function processRecursive(process, - item, - path, - visited) - if item == nil then return nil end - if visited[item] then return visited[item] end - - local processed = process(item, path) - if type(processed) == "table" then - local processedCopy = {} - visited[item] = processedCopy - local processedKey - - for k, v in rawpairs(processed) do - processedKey = processRecursive(process, k, makePath(path, k, inspect.KEY), visited) - if processedKey ~= nil then - processedCopy[processedKey] = processRecursive(process, v, makePath(path, processedKey), visited) - end - end - - local mt = processRecursive(process, getmetatable(processed), makePath(path, inspect.METATABLE), visited) - if type(mt) ~= 'table' then mt = nil end - setmetatable(processedCopy, mt) - processed = processedCopy - end - return processed +local function processRecursive(process, item, path, visited) + if item == nil then + return nil + end + if visited[item] then + return visited[item] + end + + local processed = process(item, path) + if type(processed) == "table" then + local processedCopy = {} + visited[item] = processedCopy + local processedKey + + for k, v in rawpairs(processed) do + processedKey = processRecursive(process, k, makePath(path, k, inspect.KEY), visited) + if processedKey ~= nil then + processedCopy[processedKey] = processRecursive(process, v, makePath(path, processedKey), visited) + end + end + + local mt = processRecursive(process, getmetatable(processed), makePath(path, inspect.METATABLE), visited) + if type(mt) ~= "table" then + mt = nil + end + setmetatable(processedCopy, mt) + processed = processedCopy + end + return processed end local function puts(buf, str) - buf.n = buf.n + 1 - buf[buf.n] = str + buf.n = buf.n + 1 + buf[buf.n] = str end - - local Inspector = {} - - - - - - - - - local Inspector_mt = { __index = Inspector } local function tabify(inspector) - puts(inspector.buf, inspector.newline .. rep(inspector.indent, inspector.level)) + puts(inspector.buf, inspector.newline .. rep(inspector.indent, inspector.level)) end function Inspector:getId(v) - local id = self.ids[v] - local ids = self.ids - if not id then - local tv = type(v) - id = (ids[tv] or 0) + 1 - ids[v], ids[tv] = id, id - end - return tostring(id) + local id = self.ids[v] + local ids = self.ids + if not id then + local tv = type(v) + id = (ids[tv] or 0) + 1 + ids[v], ids[tv] = id, id + end + return tostring(id) end function Inspector:putValue(v) - local buf = self.buf - local tv = type(v) - if tv == 'string' then - puts(buf, smartQuote(escape(v))) - elseif tv == 'number' or tv == 'boolean' or tv == 'nil' or - tv == 'cdata' or tv == 'ctype' then - puts(buf, tostring(v)) - elseif tv == 'table' and not self.ids[v] then - local t = v - - if t == inspect.KEY or t == inspect.METATABLE then - puts(buf, tostring(t)) - elseif self.level >= self.depth then - puts(buf, '{...}') - else - if self.cycles[t] > 1 then puts(buf, fmt('<%d>', self:getId(t))) end - - local keys, keysLen, seqLen = getKeys(t) - - puts(buf, '{') - self.level = self.level + 1 - - for i = 1, seqLen + keysLen do - if i > 1 then puts(buf, ',') end - if i <= seqLen then - puts(buf, ' ') - self:putValue(t[i]) - else - local k = keys[i - seqLen] - tabify(self) - if isIdentifier(k) then - puts(buf, k) - else - puts(buf, "[") - self:putValue(k) - puts(buf, "]") - end - puts(buf, ' = ') - self:putValue(t[k]) - end - end - - local mt = getmetatable(t) - if type(mt) == 'table' then - if seqLen + keysLen > 0 then puts(buf, ',') end - tabify(self) - puts(buf, '<metatable> = ') - self:putValue(mt) - end - - self.level = self.level - 1 - - if keysLen > 0 or type(mt) == 'table' then - tabify(self) - elseif seqLen > 0 then - puts(buf, ' ') - end - - puts(buf, '}') - end - - else - puts(buf, fmt('<%s %d>', tv, self:getId(v))) - end + local buf = self.buf + local tv = type(v) + if tv == "string" then + puts(buf, smartQuote(escape(v))) + elseif tv == "number" or tv == "boolean" or tv == "nil" or tv == "cdata" or tv == "ctype" then + puts(buf, tostring(v)) + elseif tv == "table" and not self.ids[v] then + local t = v + + if t == inspect.KEY or t == inspect.METATABLE then + puts(buf, tostring(t)) + elseif self.level >= self.depth then + puts(buf, "{...}") + else + if self.cycles[t] > 1 then + puts(buf, fmt("<%d>", self:getId(t))) + end + + local keys, keysLen, seqLen = getKeys(t) + + puts(buf, "{") + self.level = self.level + 1 + + for i = 1, seqLen + keysLen do + if i > 1 then + puts(buf, ",") + end + if i <= seqLen then + puts(buf, " ") + self:putValue(t[i]) + else + local k = keys[i - seqLen] + tabify(self) + if isIdentifier(k) then + puts(buf, k) + else + puts(buf, "[") + self:putValue(k) + puts(buf, "]") + end + puts(buf, " = ") + self:putValue(t[k]) + end + end + + local mt = getmetatable(t) + if type(mt) == "table" then + if seqLen + keysLen > 0 then + puts(buf, ",") + end + tabify(self) + puts(buf, "<metatable> = ") + self:putValue(mt) + end + + self.level = self.level - 1 + + if keysLen > 0 or type(mt) == "table" then + tabify(self) + elseif seqLen > 0 then + puts(buf, " ") + end + + puts(buf, "}") + end + else + puts(buf, fmt("<%s %d>", tv, self:getId(v))) + end end - - - function inspect.inspect(root, options) - options = options or {} + options = options or {} - local depth = options.depth or (math.huge) - local newline = options.newline or '\n' - local indent = options.indent or ' ' - local process = options.process + local depth = options.depth or math.huge + local newline = options.newline or "\n" + local indent = options.indent or " " + local process = options.process - if process then - root = processRecursive(process, root, {}, {}) - end + if process then + root = processRecursive(process, root, {}, {}) + end - local cycles = {} - countCycles(root, cycles) + local cycles = {} + countCycles(root, cycles) - local inspector = setmetatable({ - buf = { n = 0 }, - ids = {}, - cycles = cycles, - depth = depth, - level = 0, - newline = newline, - indent = indent, - }, Inspector_mt) + local inspector = setmetatable({ + buf = { n = 0 }, + ids = {}, + cycles = cycles, + depth = depth, + level = 0, + newline = newline, + indent = indent, + }, Inspector_mt) - inspector:putValue(root) + inspector:putValue(root) - return table.concat(inspector.buf) + return table.concat(inspector.buf) end setmetatable(inspect, { - __call = function(_, root, options) - return inspect.inspect(root, options) - end, + __call = function(_, root, options) + return inspect.inspect(root, options) + end, }) return inspect - diff --git a/.config/wezterm/keys.lua b/.config/wezterm/keys.lua index 0bba603..c92054b 100644 --- a/.config/wezterm/keys.lua +++ b/.config/wezterm/keys.lua @@ -1,40 +1,50 @@ -local wezterm = require "wezterm" +local wezterm = require("wezterm") local act = wezterm.action local act_callback = wezterm.action_callback local function kb(mods, key, action) - return { mods = mods, key = key, action = action } + return { mods = mods, key = key, action = action } end return { - disable_default_key_bindings = true, - keys = { - -- tabs - kb("SHIFT|CTRL", "t", act.SpawnTab "CurrentPaneDomain"), - kb("SHIFT|CTRL", "q", act.CloseCurrentTab { confirm = false }), - kb("SHIFT|CTRL", "LeftArrow", act.ActivateTabRelative(-1)), - kb("SHIFT|CTRL", "RightArrow", act.ActivateTabRelative(1)), + disable_default_key_bindings = true, + keys = { + -- tabs + kb("SHIFT|CTRL", "t", act.SpawnTab("CurrentPaneDomain")), + kb("SHIFT|CTRL", "q", act.CloseCurrentTab({ confirm = false })), + kb("SHIFT|CTRL", "LeftArrow", act.ActivateTabRelative(-1)), + kb("SHIFT|CTRL", "RightArrow", act.ActivateTabRelative(1)), - -- panes - kb("SHIFT|ALT", "w", act.SplitVertical { domain = "CurrentPaneDomain" }), - kb("SHIFT|ALT", "d", act.SplitHorizontal { domain = "CurrentPaneDomain" }), - kb("SHIFT|ALT", "q", act.CloseCurrentPane { confirm = false }), - kb("SHIFT|ALT", "UpArrow", act.ActivatePaneDirection "Up"), - kb("SHIFT|ALT", "DownArrow", act.ActivatePaneDirection "Down"), - kb("SHIFT|ALT", "LeftArrow", act.ActivatePaneDirection "Left"), - kb("SHIFT|ALT", "RightArrow", act.ActivatePaneDirection "Right"), + -- panes + kb("SHIFT|ALT", "w", act.SplitVertical({ domain = "CurrentPaneDomain" })), + kb("SHIFT|ALT", "d", act.SplitHorizontal({ domain = "CurrentPaneDomain" })), + kb("SHIFT|ALT", "q", act.CloseCurrentPane({ confirm = false })), + kb("SHIFT|ALT", "UpArrow", act.ActivatePaneDirection("Up")), + kb("SHIFT|ALT", "DownArrow", act.ActivatePaneDirection("Down")), + kb("SHIFT|ALT", "LeftArrow", act.ActivatePaneDirection("Left")), + kb("SHIFT|ALT", "RightArrow", act.ActivatePaneDirection("Right")), + + kb("", "PageUp", act.ScrollByLine(-1)), + kb("SHIFT", "PageUp", act.ScrollByLine(-5)), + kb("", "PageDown", act.ScrollByLine(1)), + kb("SHIFT", "PageDown", act.ScrollByLine(5)), - -- general - kb("CTRL", "c", act_callback(function(w, p) - local has_selection = w:get_selection_text_for_pane(p) ~= "" + -- general + kb( + "CTRL", + "c", + act_callback(function(w, p) + local has_selection = w:get_selection_text_for_pane(p) ~= "" - if has_selection then - w:perform_action(act.CopyTo "Clipboard", p) - else - w:perform_action(act.SendKey { mods = "CTRL", key = "c" }, p) - end - end)), - kb("CTRL", "v", act.PasteFrom "Clipboard"), - kb("CTRL", "f", act.Search "CurrentSelectionOrEmptyString") - } + if has_selection then + w:perform_action(act.CopyTo("Clipboard"), p) + else + w:perform_action(act.SendKey({ mods = "CTRL", key = "c" }), p) + end + end) + ), + kb("CTRL", "v", act.PasteFrom("Clipboard")), + kb("CTRL", "f", act.Search("CurrentSelectionOrEmptyString")), + kb("SHIFT|CTRL", "l", act.ShowDebugOverlay), + }, } diff --git a/.config/wezterm/stylua.toml b/.config/wezterm/stylua.toml new file mode 100644 index 0000000..5cc0b69 --- /dev/null +++ b/.config/wezterm/stylua.toml @@ -0,0 +1,5 @@ +call_parentheses = "None" +indent_type = "Spaces" + +[sort_requires] +enabled = true diff --git a/.config/wezterm/wezterm.lua b/.config/wezterm/wezterm.lua index 0cd46b6..86014f6 100644 --- a/.config/wezterm/wezterm.lua +++ b/.config/wezterm/wezterm.lua @@ -1,61 +1,122 @@ -local wezterm = require "wezterm" +local wezterm = require("wezterm") function R(name) - local m = require(name) - return m + local m = require(name) + return m +end + +-- C = { +-- bg_dark = "#161b22", +-- bg = "#1b2026", +-- +-- fg_dark = "#77828c", +-- fg = "#d9dfe4", +-- +-- black = "#1f242b", +-- red = "#df625d", +-- yellow = "#fdc267", +-- green = "#91d89a", +-- blue = "#8ec6ff", +-- pink = "#f2b9c1", +-- cyan = "#77e2e3", +-- white = "#d9dfe4", +-- +-- bright = { +-- red = "#f1726b", +-- black = "#242930", +-- yellow = "#ffd79d", +-- green = "#a1e9aa", +-- blue = "#add6ff", +-- pink = "#ffcbd2", +-- cyan = "#88f3f3", +-- white = "#d9dfe4", +-- }, +-- } + +function palette(colors) + return setmetatable(colors, { + __call = function(self) + return self[1] + end, + }) end C = { - fg = "#dfe2e7", - bg = "#222831", - fg_dark = "#8893a5", - bg_dark = "#1e232b", - - black = "#252c36", - red = "#de615c", - green = "#91d89a", - yellow = "#ffc469", - blue = "#8fc7ff", - pink = "#f2b9c1", - cyan = "#9cfdff", - white = "#dfe2e7", - - bright = { - black = "#2c3440", - red = "#e8908d", - green = "#b2e4b8", - yellow = "#ffd696", - blue = "#b1d8ff", - pink = "#f6ced4", - cyan = "#bafeff", - white = "#e9ebee", - } -} + fg = palette { + low = "#999fa7", + "#b8bec7", + high = "#d8dfe7", + }, + + 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", +} function M(...) - local mt = {} - for _,t in ipairs {...} do - for k,v in pairs(t) do - mt[k] = v - end - end - return mt + local mt = {} + for _, t in ipairs({ ... }) do + for k, v in pairs(t) do + mt[k] = v + end + end + return mt end wezterm.on("bell", function() - wezterm.background_child_process { - "notify-send", - "bell" - } + wezterm.background_child_process({ + "notify-send", + "bell", + }) end) -return M( - { - window_close_confirmation = "NeverPrompt", - check_for_updates = false - }, - R "appearance", - R "keys" -) +return M({ + window_close_confirmation = "NeverPrompt", + check_for_updates = false, + warn_about_missing_glyphs = false, +}, R("appearance"), R("keys")) -- return {} diff --git a/.dots-assets/README.md b/.dots-assets/README.md deleted file mode 100644 index 3efdd4c..0000000 --- a/.dots-assets/README.md +++ /dev/null @@ -1,4 +0,0 @@ - - -# Credits -- [Vixima](https://github.com/Vixima): Various UI sounds diff --git a/.dots-assets/preview.png b/.dots-assets/preview.png Binary files differdeleted file mode 100644 index 91db48f..0000000 --- a/.dots-assets/preview.png +++ /dev/null |