aboutsummaryrefslogtreecommitdiff
path: root/.config/awesome/quarrel
diff options
context:
space:
mode:
Diffstat (limited to '.config/awesome/quarrel')
-rw-r--r--.config/awesome/quarrel/bezier.lua343
-rw-r--r--.config/awesome/quarrel/bind.lua101
-rw-r--r--.config/awesome/quarrel/delegate.lua14
-rw-r--r--.config/awesome/quarrel/fs.lua21
-rw-r--r--.config/awesome/quarrel/init.lua21
-rw-r--r--.config/awesome/quarrel/json.lua516
-rw-r--r--.config/awesome/quarrel/lua-rust.tar.gzbin0 -> 18878 bytes
-rw-r--r--.config/awesome/quarrel/markup.lua30
-rw-r--r--.config/awesome/quarrel/math.lua9
-rw-r--r--.config/awesome/quarrel/native/Cargo.toml7
-rw-r--r--.config/awesome/quarrel/native/init.lua37
-rw-r--r--.config/awesome/quarrel/native/src/lenses/application.rs2
-rw-r--r--.config/awesome/quarrel/native/src/lenses/calculator.rs2
-rw-r--r--.config/awesome/quarrel/native/src/lib.rs22
-rw-r--r--.config/awesome/quarrel/native/src/mpd.rs142
-rw-r--r--.config/awesome/quarrel/native/src/net/mod.rs5
-rw-r--r--.config/awesome/quarrel/native/src/time.rs75
-rw-r--r--.config/awesome/quarrel/native/src/util.rs101
-rw-r--r--.config/awesome/quarrel/store.lua1
-rw-r--r--.config/awesome/quarrel/table.lua23
-rw-r--r--.config/awesome/quarrel/ui.lua94
-rw-r--r--.config/awesome/quarrel/vars.lua45
22 files changed, 1167 insertions, 444 deletions
diff --git a/.config/awesome/quarrel/bezier.lua b/.config/awesome/quarrel/bezier.lua
new file mode 100644
index 0000000..4229961
--- /dev/null
+++ b/.config/awesome/quarrel/bezier.lua
@@ -0,0 +1,343 @@
+---------------------------------------------------------------------------
+--- 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.lua b/.config/awesome/quarrel/bind.lua
index 4cb5d44..a1abf29 100644
--- a/.config/awesome/quarrel/bind.lua
+++ b/.config/awesome/quarrel/bind.lua
@@ -2,56 +2,105 @@ local awful = require "awful"
local gtable = require "gears.table"
local qstore = require "quarrel.store"
+---@class QuarrelBind
local qbind = {}
qstore.bindings = {}
-local function get_binding_function(trigger)
- if type(trigger) == "number" and trigger <= 5 and trigger > 0 then
+---@alias mouse_button
+---| 0 Left mouse button
+---| 1 Middle mouse click
+---| 2 Right mouse button
+---| 3 Scroll up
+---| 4 Scroll down
+
+---@alias bind
+---| mouse_button
+---| string
+
+---@alias modifier
+---| "Mod1" Alt
+---| "Mod4" Super
+---| "Mod5" AltGr
+---| "Shift" Shift
+---| "Control" Ctrl
+
+---@class Trigger
+---@field [1] bind
+---@field [2] any Value passed to the press callback
+
+---@class Binding
+---@field hidden boolean? Whether the binding shows up in the help menu
+---@field mods modifier[]? Modifiers
+---@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
+
+---Get the corresponding binding creation function for a trigger
+---@param bind bind
+---@return string
+local function get_binding_function(bind)
+ if type(bind) == "number" and bind <= 5 and bind > 0 then
return "button"
- elseif type(trigger) == "string" then
+ elseif type(bind) == "string" then
return "key"
end
- error("trigger can only be a mouse button or a key", 2)
+ error("bind can only be a mouse button or a key", 2)
end
-local function translate_binding(binding, trigger, multiple)
- local value = nil
- if multiple then
- value = trigger[2]
- trigger = trigger[1]
+--- Translate a qbind binding into an awful binding
+---@param binding Binding
+---@param trigger Trigger | bind
+---@param multi boolean specifies whether this is a multi trigger bind
+---@return awful.key
+local function translate_binding(binding, trigger, multi)
+ local awful_binding
+ if multi then
+ awful_binding = {
+ modifiers = binding.mods,
+ [get_binding_function(trigger[1])] = trigger[1],
+ on_press = function(...)
+ binding.press(trigger[2], ...)
+ end,
+ }
+ else
+ awful_binding = {
+ modifiers = binding.mods,
+ [get_binding_function(trigger --[[@as bind]])] = trigger,
+ on_press = binding.press,
+ }
end
- local awful_binding = {
- modifiers = binding.mods,
- [get_binding_function(trigger)] = trigger,
- on_press = multiple and function(...) binding.press(value, ...) end or binding.press
- }
+ awful_binding.description = binding.desc
+ awful_binding.group = binding.group
- if binding.desc then
- awful_binding.description = binding.desc
- end
- if binding.group then
- awful_binding.group = binding.group
- end
-
- return awful[get_binding_function(trigger)](awful_binding)
+ return awful[
+ get_binding_function(multi and trigger[1] or trigger --[[@as bind]])
+ ](awful_binding)
end
+--- Create a new binding
+---@param binding Binding
+---@return awful.key[]
function qbind:new(binding)
- if not binding.hidden then table.insert(qstore.bindings, binding) end
+ if not binding.hidden then
+ table.insert(qstore.bindings, binding)
+ end
binding.mods = binding.mods or {}
local awful_bindings = {}
if type(binding.triggers) == "table" then
- for _, trigger in ipairs(binding.triggers) do
+ for _, trigger in
+ ipairs(binding.triggers --[[@as Trigger[]])
+ do
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, false)
+ 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"
end
-- for some reason multi-trigger bindings only work if i do this
diff --git a/.config/awesome/quarrel/delegate.lua b/.config/awesome/quarrel/delegate.lua
new file mode 100644
index 0000000..54db786
--- /dev/null
+++ b/.config/awesome/quarrel/delegate.lua
@@ -0,0 +1,14 @@
+--- Capture `fn`'s upvalues and pass to `delegate`
+---@param delegate fun(env: table<string, any>, _: ...): ...
+---@param fn function
+---@return fun(...): ...
+return function(delegate, fn)
+ local upvalues = {}
+ for i = 1, debug.getinfo(fn, "u").nups do
+ local name, value = debug.getupvalue(fn, i)
+ upvalues[name] = value
+ end
+ return function(...)
+ return delegate(upvalues, ...)
+ end
+end
diff --git a/.config/awesome/quarrel/fs.lua b/.config/awesome/quarrel/fs.lua
index 89a1bc6..502f189 100644
--- a/.config/awesome/quarrel/fs.lua
+++ b/.config/awesome/quarrel/fs.lua
@@ -1,7 +1,12 @@
-local GFile = require "lgi".Gio.File
+local GFile = require("lgi").Gio.File
+---@class QuarrelFs
local qfs = {}
+--- Read a file with the specified format (or "a") and close the file
+---@param path string
+---@param format openmode
+---@return any
function qfs.read(path, format)
local f = assert(io.open(path, "r"))
local c = f:read(format or "a")
@@ -9,13 +14,23 @@ function qfs.read(path, format)
return c
end
+--- List files in a directory
+---@param path string
+---@param absolute boolean?
+---@return table
function qfs.ls_files(path, absolute)
local files = GFile.new_for_path(path):enumerate_children("standard::*", 0)
local files_filtered = {}
- if not files then return {} end
+ if not files then
+ return {}
+ end
- for file in function() return files:next_file() end do
+ for file in
+ function()
+ return files:next_file()
+ end
+ do
if file:get_file_type() == "REGULAR" then
local file_name = file:get_display_name()
file_name = absolute and (path:gsub("[/]*$", "") .. "/" .. file_name) or file_name
diff --git a/.config/awesome/quarrel/init.lua b/.config/awesome/quarrel/init.lua
index 025d899..96f7f5b 100644
--- a/.config/awesome/quarrel/init.lua
+++ b/.config/awesome/quarrel/init.lua
@@ -1,17 +1,22 @@
-local n = require "naughty".notification
+local n = require("naughty").notification
+---@class Quarrel
local quarrel = {}
+--- Send a notification with the specified message
+---@param message any
function quarrel.debug(message)
n { message = tostring(message) }
end
-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
+--- 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/json.lua b/.config/awesome/quarrel/json.lua
index 711ef78..5bcc5ff 100644
--- a/.config/awesome/quarrel/json.lua
+++ b/.config/awesome/quarrel/json.lua
@@ -31,111 +31,103 @@ local json = { _version = "0.1.2" }
local encode
local escape_char_map = {
- [ "\\" ] = "\\",
- [ "\"" ] = "\"",
- [ "\b" ] = "b",
- [ "\f" ] = "f",
- [ "\n" ] = "n",
- [ "\r" ] = "r",
- [ "\t" ] = "t",
+ ["\\"] = "\\",
+ ['"'] = '"',
+ ["\b"] = "b",
+ ["\f"] = "f",
+ ["\n"] = "n",
+ ["\r"] = "r",
+ ["\t"] = "t",
}
-local escape_char_map_inv = { [ "/" ] = "/" }
+local escape_char_map_inv = { ["/"] = "/" }
for k, v in pairs(escape_char_map) do
- escape_char_map_inv[v] = k
+ escape_char_map_inv[v] = k
end
-
local function escape_char(c)
- return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte()))
+ return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte()))
end
-
local function encode_nil(val)
- return "null"
+ return "null"
end
-
local function encode_table(val, stack)
- local res = {}
- stack = stack or {}
-
- -- Circular reference?
- if stack[val] then error("circular reference") end
-
- stack[val] = true
-
- if rawget(val, 1) ~= nil or next(val) == nil then
- -- Treat as array -- check keys are valid and it is not sparse
- local n = 0
- for k in pairs(val) do
- if type(k) ~= "number" then
- error("invalid table: mixed or invalid key types")
- end
- n = n + 1
- end
- if n ~= #val then
- error("invalid table: sparse array")
- end
- -- Encode
- for i, v in ipairs(val) do
- table.insert(res, encode(v, stack))
+ local res = {}
+ stack = stack or {}
+
+ -- Circular reference?
+ if stack[val] then
+ error "circular reference"
end
- stack[val] = nil
- return "[" .. table.concat(res, ",") .. "]"
-
- else
- -- Treat as an object
- for k, v in pairs(val) do
- if type(k) ~= "string" then
- error("invalid table: mixed or invalid key types")
- end
- table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
+
+ stack[val] = true
+
+ if rawget(val, 1) ~= nil or next(val) == nil then
+ -- Treat as array -- check keys are valid and it is not sparse
+ local n = 0
+ for k in pairs(val) do
+ if type(k) ~= "number" then
+ error "invalid table: mixed or invalid key types"
+ end
+ n = n + 1
+ end
+ if n ~= #val then
+ error "invalid table: sparse array"
+ end
+ -- Encode
+ for i, v in ipairs(val) do
+ table.insert(res, encode(v, stack))
+ end
+ stack[val] = nil
+ return "[" .. table.concat(res, ",") .. "]"
+ else
+ -- Treat as an object
+ for k, v in pairs(val) do
+ if type(k) ~= "string" then
+ error "invalid table: mixed or invalid key types"
+ end
+ table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
+ end
+ stack[val] = nil
+ return "{" .. table.concat(res, ",") .. "}"
end
- stack[val] = nil
- return "{" .. table.concat(res, ",") .. "}"
- end
end
-
local function encode_string(val)
- return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
+ return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
end
-
local function encode_number(val)
- -- Check for NaN, -inf and inf
- if val ~= val or val <= -math.huge or val >= math.huge then
- error("unexpected number value '" .. tostring(val) .. "'")
- end
- return string.format("%.14g", val)
+ -- Check for NaN, -inf and inf
+ if val ~= val or val <= -math.huge or val >= math.huge then
+ error("unexpected number value '" .. tostring(val) .. "'")
+ end
+ return string.format("%.14g", val)
end
-
local type_func_map = {
- [ "nil" ] = encode_nil,
- [ "table" ] = encode_table,
- [ "string" ] = encode_string,
- [ "number" ] = encode_number,
- [ "boolean" ] = tostring,
+ ["nil"] = encode_nil,
+ ["table"] = encode_table,
+ ["string"] = encode_string,
+ ["number"] = encode_number,
+ ["boolean"] = tostring,
}
-
encode = function(val, stack)
- local t = type(val)
- local f = type_func_map[t]
- if f then
- return f(val, stack)
- end
- error("unexpected type '" .. t .. "'")
+ local t = type(val)
+ local f = type_func_map[t]
+ if f then
+ return f(val, stack)
+ end
+ error("unexpected type '" .. t .. "'")
end
-
function json.encode(val)
- return ( encode(val) )
+ return (encode(val))
end
-
-------------------------------------------------------------------------------
-- Decode
-------------------------------------------------------------------------------
@@ -143,246 +135,238 @@ end
local parse
local function create_set(...)
- local res = {}
- for i = 1, select("#", ...) do
- res[ select(i, ...) ] = true
- end
- return res
+ local res = {}
+ for i = 1, select("#", ...) do
+ res[select(i, ...)] = true
+ end
+ return res
end
-local space_chars = create_set(" ", "\t", "\r", "\n")
-local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
-local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
-local literals = create_set("true", "false", "null")
+local space_chars = create_set(" ", "\t", "\r", "\n")
+local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
+local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
+local literals = create_set("true", "false", "null")
local literal_map = {
- [ "true" ] = true,
- [ "false" ] = false,
- [ "null" ] = nil,
+ ["true"] = true,
+ ["false"] = false,
+ ["null"] = nil,
}
-
local function next_char(str, idx, set, negate)
- for i = idx, #str do
- if set[str:sub(i, i)] ~= negate then
- return i
+ for i = idx, #str do
+ if set[str:sub(i, i)] ~= negate then
+ return i
+ end
end
- end
- return #str + 1
+ return #str + 1
end
-
local function decode_error(str, idx, msg)
- local line_count = 1
- local col_count = 1
- for i = 1, idx - 1 do
- col_count = col_count + 1
- if str:sub(i, i) == "\n" then
- line_count = line_count + 1
- col_count = 1
+ local line_count = 1
+ local col_count = 1
+ for i = 1, idx - 1 do
+ col_count = col_count + 1
+ if str:sub(i, i) == "\n" then
+ line_count = line_count + 1
+ col_count = 1
+ end
end
- end
- error( string.format("%s at line %d col %d", msg, line_count, col_count) )
+ error(string.format("%s at line %d col %d", msg, line_count, col_count))
end
-
local function codepoint_to_utf8(n)
- -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
- local f = math.floor
- if n <= 0x7f then
- return string.char(n)
- elseif n <= 0x7ff then
- return string.char(f(n / 64) + 192, n % 64 + 128)
- elseif n <= 0xffff then
- return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
- elseif n <= 0x10ffff then
- return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
- f(n % 4096 / 64) + 128, n % 64 + 128)
- end
- error( string.format("invalid unicode codepoint '%x'", n) )
+ -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
+ local f = math.floor
+ if n <= 0x7f then
+ return string.char(n)
+ elseif n <= 0x7ff then
+ return string.char(f(n / 64) + 192, n % 64 + 128)
+ elseif n <= 0xffff then
+ return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
+ elseif n <= 0x10ffff then
+ return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, f(n % 4096 / 64) + 128, n % 64 + 128)
+ end
+ error(string.format("invalid unicode codepoint '%x'", n))
end
-
local function parse_unicode_escape(s)
- local n1 = tonumber( s:sub(1, 4), 16 )
- local n2 = tonumber( s:sub(7, 10), 16 )
- -- Surrogate pair?
- if n2 then
- return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
- else
- return codepoint_to_utf8(n1)
- end
+ local n1 = tonumber(s:sub(1, 4), 16)
+ local n2 = tonumber(s:sub(7, 10), 16)
+ -- Surrogate pair?
+ if n2 then
+ return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
+ else
+ return codepoint_to_utf8(n1)
+ end
end
-
local function parse_string(str, i)
- local res = ""
- local j = i + 1
- local k = j
-
- while j <= #str do
- local x = str:byte(j)
-
- if x < 32 then
- decode_error(str, j, "control character in string")
-
- elseif x == 92 then -- `\`: Escape
- res = res .. str:sub(k, j - 1)
- j = j + 1
- local c = str:sub(j, j)
- if c == "u" then
- local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1)
- or str:match("^%x%x%x%x", j + 1)
- or decode_error(str, j - 1, "invalid unicode escape in string")
- res = res .. parse_unicode_escape(hex)
- j = j + #hex
- else
- if not escape_chars[c] then
- decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string")
+ local res = ""
+ local j = i + 1
+ local k = j
+
+ while j <= #str do
+ local x = str:byte(j)
+
+ if x < 32 then
+ decode_error(str, j, "control character in string")
+ elseif x == 92 then -- `\`: Escape
+ res = res .. str:sub(k, j - 1)
+ j = j + 1
+ local c = str:sub(j, j)
+ if c == "u" then
+ local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1)
+ or str:match("^%x%x%x%x", j + 1)
+ or decode_error(str, j - 1, "invalid unicode escape in string")
+ res = res .. parse_unicode_escape(hex)
+ j = j + #hex
+ else
+ if not escape_chars[c] then
+ decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string")
+ end
+ res = res .. escape_char_map_inv[c]
+ end
+ k = j + 1
+ elseif x == 34 then -- `"`: End of string
+ res = res .. str:sub(k, j - 1)
+ return res, j + 1
end
- res = res .. escape_char_map_inv[c]
- end
- k = j + 1
- elseif x == 34 then -- `"`: End of string
- res = res .. str:sub(k, j - 1)
- return res, j + 1
+ j = j + 1
end
- j = j + 1
- end
-
- decode_error(str, i, "expected closing quote for string")
+ decode_error(str, i, "expected closing quote for string")
end
-
local function parse_number(str, i)
- local x = next_char(str, i, delim_chars)
- local s = str:sub(i, x - 1)
- local n = tonumber(s)
- if not n then
- decode_error(str, i, "invalid number '" .. s .. "'")
- end
- return n, x
+ local x = next_char(str, i, delim_chars)
+ local s = str:sub(i, x - 1)
+ local n = tonumber(s)
+ if not n then
+ decode_error(str, i, "invalid number '" .. s .. "'")
+ end
+ return n, x
end
-
local function parse_literal(str, i)
- local x = next_char(str, i, delim_chars)
- local word = str:sub(i, x - 1)
- if not literals[word] then
- decode_error(str, i, "invalid literal '" .. word .. "'")
- end
- return literal_map[word], x
+ local x = next_char(str, i, delim_chars)
+ local word = str:sub(i, x - 1)
+ if not literals[word] then
+ decode_error(str, i, "invalid literal '" .. word .. "'")
+ end
+ return literal_map[word], x
end
-
local function parse_array(str, i)
- local res = {}
- local n = 1
- i = i + 1
- while 1 do
- local x
- i = next_char(str, i, space_chars, true)
- -- Empty / end of array?
- if str:sub(i, i) == "]" then
- i = i + 1
- break
- end
- -- Read token
- x, i = parse(str, i)
- res[n] = x
- n = n + 1
- -- Next token
- i = next_char(str, i, space_chars, true)
- local chr = str:sub(i, i)
+ local res = {}
+ local n = 1
i = i + 1
- if chr == "]" then break end
- if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
- end
- return res, i
+ while 1 do
+ local x
+ i = next_char(str, i, space_chars, true)
+ -- Empty / end of array?
+ if str:sub(i, i) == "]" then
+ i = i + 1
+ break
+ end
+ -- Read token
+ x, i = parse(str, i)
+ res[n] = x
+ n = n + 1
+ -- Next token
+ i = next_char(str, i, space_chars, true)
+ local chr = str:sub(i, i)
+ i = i + 1
+ if chr == "]" then
+ break
+ end
+ if chr ~= "," then
+ decode_error(str, i, "expected ']' or ','")
+ end
+ end
+ return res, i
end
-
local function parse_object(str, i)
- local res = {}
- i = i + 1
- while 1 do
- local key, val
- i = next_char(str, i, space_chars, true)
- -- Empty / end of object?
- if str:sub(i, i) == "}" then
- i = i + 1
- break
- end
- -- Read key
- if str:sub(i, i) ~= '"' then
- decode_error(str, i, "expected string for key")
- end
- key, i = parse(str, i)
- -- Read ':' delimiter
- i = next_char(str, i, space_chars, true)
- if str:sub(i, i) ~= ":" then
- decode_error(str, i, "expected ':' after key")
- end
- i = next_char(str, i + 1, space_chars, true)
- -- Read value
- val, i = parse(str, i)
- -- Set
- res[key] = val
- -- Next token
- i = next_char(str, i, space_chars, true)
- local chr = str:sub(i, i)
+ local res = {}
i = i + 1
- if chr == "}" then break end
- if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
- end
- return res, i
+ while 1 do
+ local key, val
+ i = next_char(str, i, space_chars, true)
+ -- Empty / end of object?
+ if str:sub(i, i) == "}" then
+ i = i + 1
+ break
+ end
+ -- Read key
+ if str:sub(i, i) ~= '"' then
+ decode_error(str, i, "expected string for key")
+ end
+ key, i = parse(str, i)
+ -- Read ':' delimiter
+ i = next_char(str, i, space_chars, true)
+ if str:sub(i, i) ~= ":" then
+ decode_error(str, i, "expected ':' after key")
+ end
+ i = next_char(str, i + 1, space_chars, true)
+ -- Read value
+ val, i = parse(str, i)
+ -- Set
+ res[key] = val
+ -- Next token
+ i = next_char(str, i, space_chars, true)
+ local chr = str:sub(i, i)
+ i = i + 1
+ if chr == "}" then
+ break
+ end
+ if chr ~= "," then
+ decode_error(str, i, "expected '}' or ','")
+ end
+ end
+ return res, i
end
-
local char_func_map = {
- [ '"' ] = parse_string,
- [ "0" ] = parse_number,
- [ "1" ] = parse_number,
- [ "2" ] = parse_number,
- [ "3" ] = parse_number,
- [ "4" ] = parse_number,
- [ "5" ] = parse_number,
- [ "6" ] = parse_number,
- [ "7" ] = parse_number,
- [ "8" ] = parse_number,
- [ "9" ] = parse_number,
- [ "-" ] = parse_number,
- [ "t" ] = parse_literal,
- [ "f" ] = parse_literal,
- [ "n" ] = parse_literal,
- [ "[" ] = parse_array,
- [ "{" ] = parse_object,
+ ['"'] = parse_string,
+ ["0"] = parse_number,
+ ["1"] = parse_number,
+ ["2"] = parse_number,
+ ["3"] = parse_number,
+ ["4"] = parse_number,
+ ["5"] = parse_number,
+ ["6"] = parse_number,
+ ["7"] = parse_number,
+ ["8"] = parse_number,
+ ["9"] = parse_number,
+ ["-"] = parse_number,
+ ["t"] = parse_literal,
+ ["f"] = parse_literal,
+ ["n"] = parse_literal,
+ ["["] = parse_array,
+ ["{"] = parse_object,
}
-
parse = function(str, idx)
- local chr = str:sub(idx, idx)
- local f = char_func_map[chr]
- if f then
- return f(str, idx)
- end
- decode_error(str, idx, "unexpected character '" .. chr .. "'")
+ local chr = str:sub(idx, idx)
+ local f = char_func_map[chr]
+ if f then
+ return f(str, idx)
+ end
+ decode_error(str, idx, "unexpected character '" .. chr .. "'")
end
-
function json.decode(str)
- if type(str) ~= "string" then
- error("expected argument of type string, got " .. type(str))
- end
- local res, idx = parse(str, next_char(str, 1, space_chars, true))
- idx = next_char(str, idx, space_chars, true)
- if idx <= #str then
- decode_error(str, idx, "trailing garbage")
- end
- return res
+ if type(str) ~= "string" then
+ error("expected argument of type string, got " .. type(str))
+ end
+ local res, idx = parse(str, next_char(str, 1, space_chars, true))
+ idx = next_char(str, idx, space_chars, true)
+ if idx <= #str then
+ decode_error(str, idx, "trailing garbage")
+ end
+ return res
end
-
return json
diff --git a/.config/awesome/quarrel/lua-rust.tar.gz b/.config/awesome/quarrel/lua-rust.tar.gz
new file mode 100644
index 0000000..5ff33fd
--- /dev/null
+++ b/.config/awesome/quarrel/lua-rust.tar.gz
Binary files differ
diff --git a/.config/awesome/quarrel/markup.lua b/.config/awesome/quarrel/markup.lua
new file mode 100644
index 0000000..d206530
--- /dev/null
+++ b/.config/awesome/quarrel/markup.lua
@@ -0,0 +1,30 @@
+--- Apply markup to a file
+---@param content string
+---@param args { bold: boolean, italic: boolean, fg: string, bg: string }
+---@return string
+return function(content, args)
+ args = args or {}
+ if args.bold then
+ content = "<b>" .. content .. "</b>"
+ end
+
+ if args.italic then
+ content = "<i>" .. content .. "</i>"
+ end
+
+ local span_content = ""
+
+ if args.fg or args.bg then
+ if args.fg then
+ span_content = "foreground='" .. args.fg .. "'"
+ end
+
+ if args.bg then
+ span_content = " background='" .. args.bg .. "'"
+ end
+
+ content = "<span " .. span_content .. ">" .. content .. "</span>"
+ end
+
+ return content
+end
diff --git a/.config/awesome/quarrel/math.lua b/.config/awesome/quarrel/math.lua
index 886ce13..b16547b 100644
--- a/.config/awesome/quarrel/math.lua
+++ b/.config/awesome/quarrel/math.lua
@@ -1,5 +1,10 @@
+local gmath = require "gears.math"
+
+---@class QuarrelMath
local qmath = {}
+-- TODO: Finish documenting these functions
+
function qmath.step_value(value, steps)
if value > steps[#steps - 1][1] then
return steps[#steps - 1][2]
@@ -15,6 +20,10 @@ function qmath.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)
+ return gmath.round(value / max * 100)
+end
+
function qmath.clamp(value, min, max)
return math.max(math.min(value, max), min)
end
diff --git a/.config/awesome/quarrel/native/Cargo.toml b/.config/awesome/quarrel/native/Cargo.toml
index 9cff8fc..8d56c9f 100644
--- a/.config/awesome/quarrel/native/Cargo.toml
+++ b/.config/awesome/quarrel/native/Cargo.toml
@@ -17,6 +17,13 @@ nix = "0.26.2"
chrono = "0.4.24"
itertools = "0.10.5"
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"
+once_cell = "1.18.0"
+
[lib]
crate-type = ["cdylib"]
diff --git a/.config/awesome/quarrel/native/init.lua b/.config/awesome/quarrel/native/init.lua
index 14c66e5..e5d5aab 100644
--- a/.config/awesome/quarrel/native/init.lua
+++ b/.config/awesome/quarrel/native/init.lua
@@ -1,6 +1,43 @@
+---@meta
+
local old_cpath = package.cpath
local cfg = require("gears.filesystem").get_configuration_dir()
package.cpath = package.cpath .. ";" .. cfg .. "quarrel/native/lib?.so"
+
+---@class Entry
+---@field message string
+---@field exec { [1]: string, [2]: boolean }?
+
+---@alias query fun(input: string): Entry[]
+
+---@class Lenses
+---@field [1] query Calculator lense
+---@field [2] query Application lense
+
+---@alias ReadMode "l" | "n" | string
+
+---@class FileHandle
+---@field read fun(self, mode: ReadMode): string | number
+---@field write fun(self, content: string): number
+---@field lines fun(self): string[]
+---@field rewind fun(self)
+
+---@class Util
+---@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"
+
package.cpath = old_cpath
return qnative
diff --git a/.config/awesome/quarrel/native/src/lenses/application.rs b/.config/awesome/quarrel/native/src/lenses/application.rs
index 0857802..72aba8d 100644
--- a/.config/awesome/quarrel/native/src/lenses/application.rs
+++ b/.config/awesome/quarrel/native/src/lenses/application.rs
@@ -73,7 +73,7 @@ pub fn query(lua: &Lua, input: String) -> LuaResult<LuaTable> {
return None
};
- return parse_entry(&entry, &path).ok();
+ parse_entry(&entry, &path).ok()
})
.collect();
diff --git a/.config/awesome/quarrel/native/src/lenses/calculator.rs b/.config/awesome/quarrel/native/src/lenses/calculator.rs
index c79dd42..07f1ee2 100644
--- a/.config/awesome/quarrel/native/src/lenses/calculator.rs
+++ b/.config/awesome/quarrel/native/src/lenses/calculator.rs
@@ -10,7 +10,7 @@ use crate::lenses::entry::{
Entry,
};
-pub fn query<'a>(lua: &Lua, input: String) -> LuaResult<LuaTable> {
+pub fn query(lua: &Lua, input: String) -> LuaResult<LuaTable> {
let result = match eval(input.trim(), true, Unit::Celsius, false) {
Ok(result) => {
format!("{result}")
diff --git a/.config/awesome/quarrel/native/src/lib.rs b/.config/awesome/quarrel/native/src/lib.rs
index d89b610..472313e 100644
--- a/.config/awesome/quarrel/native/src/lib.rs
+++ b/.config/awesome/quarrel/native/src/lib.rs
@@ -1,8 +1,9 @@
mod lenses;
+mod mpd;
mod net;
+mod util;
use mlua::prelude::*;
-use html_escape::decode_html_entities_to_string;
#[mlua::lua_module]
fn qnative(lua: &Lua) -> LuaResult<LuaTable> {
@@ -10,14 +11,21 @@ fn qnative(lua: &Lua) -> LuaResult<LuaTable> {
lenses.set("1", lua.create_function(lenses::calculator::query)?)?;
lenses.set("2", lua.create_function(lenses::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("get_essid", lua.create_function(net::get_first_essid)?)?;
- exports.set("decode_html", lua.create_function(|_: &Lua, string: String| {
- let mut output = String::new();
- decode_html_entities_to_string(string, &mut output);
- Ok(output)
- })?)?;
+ exports.set("mpd", mpd)?;
+ exports.set("net", net)?;
+ exports.set("util", util)?;
Ok(exports)
}
diff --git a/.config/awesome/quarrel/native/src/mpd.rs b/.config/awesome/quarrel/native/src/mpd.rs
new file mode 100644
index 0000000..08c9e42
--- /dev/null
+++ b/.config/awesome/quarrel/native/src/mpd.rs
@@ -0,0 +1,142 @@
+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 71eaeea..96c853e 100644
--- a/.config/awesome/quarrel/native/src/net/mod.rs
+++ b/.config/awesome/quarrel/native/src/net/mod.rs
@@ -39,7 +39,12 @@ use wireless::{
ioctl_read_bad!(ioctl_get_interfaces, SIOCGIFCONF, IfConf);
ioctl_read_bad!(ioctl_get_essid, SIOCGIWESSID, IwReq);
+#[allow(clippy::unnecessary_wraps)]
pub fn get_first_essid(_: &Lua, _: ()) -> LuaResult<String> {
+ Ok(get_first_essid_error().unwrap_or(String::new()))
+}
+
+fn get_first_essid_error() -> LuaResult<String> {
type Buffer = [c_char; 1024];
let mut buffer: Buffer = [0; 1024];
diff --git a/.config/awesome/quarrel/native/src/time.rs b/.config/awesome/quarrel/native/src/time.rs
deleted file mode 100644
index 9850822..0000000
--- a/.config/awesome/quarrel/native/src/time.rs
+++ /dev/null
@@ -1,75 +0,0 @@
-use chrono::prelude::*;
-use mlua::{
- prelude::*,
- LuaSerdeExt,
-};
-
-// Taken from https://github.com/chronotope/chrono/issues/69#issuecomment-1510506428
-trait NaiveDateExt {
- fn days_in_month(&self) -> u32;
- fn days_in_year(&self) -> u32;
- fn is_leap_year(&self) -> bool;
-}
-
-impl NaiveDateExt for NaiveDate {
- fn days_in_month(&self) -> u32 {
- let month = self.month();
- match month {
- 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
- 4 | 6 | 9 | 11 => 30,
- 2 => {
- if self.is_leap_year() {
- 29
- } else {
- 28
- }
- }
- _ => panic!("Invalid month: {}", month),
- }
- }
-
- fn days_in_year(&self) -> u32 {
- if self.is_leap_year() {
- 366
- } else {
- 365
- }
- }
-
- fn is_leap_year(&self) -> bool {
- let year = self.year();
- return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
- }
-}
-
-struct Day {
- day: u32,
- ce: bool,
- weekday: Weekday,
-}
-
-pub fn get_calendar_table(lua: &Lua, (year, month, day): (i32, u32, u32)) -> LuaResult<LuaTable> {
- let date =
- NaiveDate::from_ymd_opt(year, month, day).ok_or(LuaError::external("invalid date"))?;
- let days: Vec<Day> = (1..=date.days_in_month())
- .map(|day| NaiveDate::from_ymd_opt(date.year(), date.month(), day).unwrap())
- .map(|date| {
- let (ce, year) = date.year_ce();
- Day {
- day: date.day(),
- ce,
- weekday: date.weekday(),
- }
- })
- .collect();
-
- let calendar: Vec<Vec<Day>> = vec![vec![], vec![], vec![], vec![], vec![], vec![], vec![]];
-
- if days[1].weekday != Weekday::Mon {
- get_calendar_table(lua)
- }
-
- if days.last().unwrap().weekday != Weekday::Sun {}
-
- Ok(lua.create_table()?)
-}
diff --git a/.config/awesome/quarrel/native/src/util.rs b/.config/awesome/quarrel/native/src/util.rs
new file mode 100644
index 0000000..85a2574
--- /dev/null
+++ b/.config/awesome/quarrel/native/src/util.rs
@@ -0,0 +1,101 @@
+use std::{
+ cell::RefCell,
+ fs::File,
+ fs::OpenOptions,
+ io::{
+ Read,
+ Seek,
+ Write
+ },
+ rc::Rc,
+ str::FromStr,
+};
+
+use html_escape::decode_html_entities_to_string;
+use mlua::prelude::*;
+use serde::Serialize;
+
+pub fn decode_html(_: &Lua, string: String) -> LuaResult<String> {
+ let mut output = String::new();
+ decode_html_entities_to_string(string, &mut output);
+ Ok(output)
+}
+
+enum ReadMode {
+ Line,
+ Number,
+ All,
+}
+
+impl FromStr for ReadMode {
+ type Err = ();
+
+ fn from_str(value: &str) -> Result<Self, Self::Err> {
+ Ok(match value {
+ "l" => Self::Line,
+ "n" => Self::Number,
+ _ => Self::All,
+ })
+ }
+}
+
+#[derive(Serialize)]
+#[serde(untagged)]
+enum StringOrNumber {
+ String(String),
+ Number(u64),
+}
+
+pub struct FileHandle(Rc<RefCell<File>>);
+
+impl LuaUserData for FileHandle {
+ fn add_methods<'lua, M: LuaUserDataMethods<'lua, 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() {
+ ReadMode::Line => StringOrNumber::String(
+ content
+ .lines()
+ .next()
+ .map_or_else(String::new, |slice| slice.trim().to_owned()),
+ ),
+ ReadMode::Number => StringOrNumber::Number(
+ content.trim().parse::<u64>().map_err(LuaError::external)?,
+ ),
+ ReadMode::All => StringOrNumber::String(content),
+ }))
+ });
+ methods.add_method("write", |_, this: &FileHandle, content: String| {
+ this.write(content.as_bytes())
+ });
+ methods.add_method("lines", |_, this: &FileHandle, (): ()| {
+ Ok(this
+ .read()?
+ .lines()
+ .map(ToOwned::to_owned)
+ .collect::<Vec<String>>())
+ });
+ methods.add_method("rewind", |_, this: &FileHandle, (): ()| this.rewind());
+ }
+}
+
+impl FileHandle {
+ pub fn new(_: &Lua, path: String) -> LuaResult<Self> {
+ Ok(Self(Rc::new(RefCell::new(File::open(path)?))))
+ // Ok(Self(Rc::new(RefCell::new(OpenOptions::new().write(true).read(true).open(path)?))))
+ }
+
+ fn read(&self) -> LuaResult<String> {
+ let mut content = String::new();
+ self.0.borrow_mut().read_to_string(&mut content)?;
+ Ok(content)
+ }
+
+ fn rewind(&self) -> LuaResult<()> {
+ self.0.borrow_mut().rewind().map_err(LuaError::external)
+ }
+
+ fn write(&self, buf: &[u8]) -> LuaResult<usize> {
+ self.0.borrow_mut().write(buf).map_err(LuaError::external)
+ }
+}
diff --git a/.config/awesome/quarrel/store.lua b/.config/awesome/quarrel/store.lua
index 9f6cff2..9422c21 100644
--- a/.config/awesome/quarrel/store.lua
+++ b/.config/awesome/quarrel/store.lua
@@ -1,3 +1,4 @@
+---@type table
local qstore = {}
return qstore
diff --git a/.config/awesome/quarrel/table.lua b/.config/awesome/quarrel/table.lua
index 13ccbce..2ae15ee 100644
--- a/.config/awesome/quarrel/table.lua
+++ b/.config/awesome/quarrel/table.lua
@@ -1,17 +1,30 @@
+---@class QuarrelTable
local qtable = {}
+--- Map a function on each element in the table
+---@param t table
+---@generic T
+---@param f fun(v: T): T
+---@return table
function qtable.map(t, f)
local nt = {}
- for k,v in pairs(t) do
+ for k, v in pairs(t) do
nt[k] = f(v)
end
return nt
end
+--- Filter a table with a function
+---@param t table
+---@param f fun(v: any): boolean
+---@param dict boolean Whether the supplied table is a dictionary
+---@return table
function qtable.filter(t, f, dict)
local nt = {}
- for k,v in pairs(t) do
- if not f(v) then goto continue end
+ for k, v in pairs(t) do
+ if not f(v) then
+ goto continue
+ end
if dict then
nt[k] = v
else
@@ -40,9 +53,9 @@ function qtable.onext(t, state)
t.__oindex = __gen_oindex(t)
key = t.__oindex[1]
else
- for i = 1,#t.__oindex do
+ for i = 1, #t.__oindex do
if t.__oindex[i] == state then
- key = t.__oindex[i+1]
+ key = t.__oindex[i + 1]
end
end
end
diff --git a/.config/awesome/quarrel/ui.lua b/.config/awesome/quarrel/ui.lua
index 274d48a..1db4a70 100644
--- a/.config/awesome/quarrel/ui.lua
+++ b/.config/awesome/quarrel/ui.lua
@@ -2,69 +2,68 @@ 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 = {}
-function qui.markup_fg(color, text)
- return "<span color=\"" .. color .. "\">" .. text .. "</span>"
-end
-
-function qui.markup_bg(color, text)
- return "<span bgcolor=\"" .. color .. "\">" .. text .. "</span>"
-end
-
+--- 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
+ 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
+ target.widget,
}
return awful.popup(qui.styled(target))
end
-function qui.tooltip(objects, callback)
- awful.tooltip(qui.styled {
- objects = objects,
- timer_function = callback,
- margin_leftright = qvars.padding,
- margin_topbottom = qvars.padding
- })
-end
-
+--- Generate svg recolor string
+---@param color string
+---@return string
function qui.recolor(color)
return "svg{fill:" .. color .. "}"
end
-function qui.icon(image, color, target)
- local widget = {
+--- Generate icon widget
+---@param args table
+---@return table
+function qui.icon(args)
+ return gtable.crush({
widget = wibox.widget.imagebox,
- image = image,
+ image = args.icon,
forced_width = qvars.char_height,
forced_height = qvars.char_height,
- stylesheet = qui.recolor(color or qvars.colors.fg)
- }
- if target then
- return gtable.crush(widget, target)
- else
- return widget
- end
+ 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({
@@ -73,7 +72,7 @@ function qui.button(args)
forced_height = qvars.char_height,
forced_width = qvars.char_height,
stylesheet = qui.recolor(qvars.colors.fg),
- press = args.press
+ press = args.press,
}, args.widget or {}))
widget.buttons = {
@@ -82,16 +81,19 @@ function qui.button(args)
press = function()
widget:press()
end,
- hidden = true
- }
+ 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({
+ local widget = qui.button {
widget = gtable.crush({
toggled = false,
silent_press = function(self, state)
@@ -100,7 +102,7 @@ function qui.toggle(args)
else
self.toggled = not self.toggled
end
-
+
if self.toggled then
self.image = args.on
else
@@ -114,10 +116,30 @@ function qui.toggle(args)
self:silent_press()
end
args.press(self)
- end
- })
+ 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/vars.lua b/.config/awesome/quarrel/vars.lua
index 27da3f2..1983343 100644
--- a/.config/awesome/quarrel/vars.lua
+++ b/.config/awesome/quarrel/vars.lua
@@ -1,19 +1,26 @@
-local btns = require "awful".button.names
+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 wibox = require "wibox"
+---@class QuarrelVars
local qvars = {}
-qvars.anim_duration = 0.15
+qvars.anim_duration = 0.20
qvars.anim_intro = qvars.anim_duration / 4
-qvars.notif_timeout = 3 + qvars.anim_duration * 2
+qvars.notif_timeout = 3
-function qvars.shape(cr,w,h)
- gears.shape.rounded_rect(cr,w,h,dpi(4))
+--- 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)
@@ -21,14 +28,19 @@ 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.font_size = 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
-local char_width, char_height = wibox.widget {
- widget = wibox.widget.textbox,
- text = "a"
-}:get_preferred_size_at_dpi(screen[1].dpi)
+local char_width, char_height = wibox
+ .widget({
+ widget = wibox.widget.textbox,
+ text = "a",
+ })
+ :get_preferred_size_at_dpi(awful.screen.focused().dpi)
qvars.char_height = char_height
qvars.char_width = char_width
@@ -62,11 +74,11 @@ qvars.colors = {
},
dim = {
- fg = "#8893a5",
- bg = "#20262e"
+ fg = "#77828c",
+ bg = "#161b22",
},
- transparent = "#00000000"
+ transparent = "#00000000",
}
-- taken from https://github.com/bew/dotfiles/blob/ab9bb1935783f7a31ef777b1d7e26d53f35df864/gui/wezterm/cfg_utils.lua
@@ -77,15 +89,16 @@ qvars.mods = setmetatable({ _SHORT_MAP = { C = "Control", S = "Shift", A = "Mod1
resolved_mods[i] = self._SHORT_MAP[key:sub(i, i)]
end
return resolved_mods
- end
+ end,
})
+---@enum buttons
qvars.btns = {
left = btns.LEFT,
right = btns.RIGHT,
middle = btns.MIDDLE,
up = btns.SCROLL_UP,
- down = btns.SCROLL_DOWN
+ down = btns.SCROLL_DOWN,
}
return qvars