diff options
author | delta <darkussdelta@gmail.com> | 2024-03-05 14:48:59 +0100 |
---|---|---|
committer | delta <darkussdelta@gmail.com> | 2024-03-05 14:48:59 +0100 |
commit | 510ef8e178929cf5e0c7fd5a5120fecf5f1b79f2 (patch) | |
tree | 3582e5cd7d000335ca94f2a009f3aed57bd86919 /.config/awesome/quarrel | |
parent | 95ba8030f722a616cff06c122dcfb2f63e25cf45 (diff) |
idk anymore
Diffstat (limited to '.config/awesome/quarrel')
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 Binary files differnew file mode 100644 index 0000000..5ff33fd --- /dev/null +++ b/.config/awesome/quarrel/lua-rust.tar.gz 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 |