Merge pull request #2 from copycat-killer/master

Merge to master
This commit is contained in:
trap000d 2017-02-06 14:34:55 +13:00 committed by GitHub
commit 56ebe9158e
61 changed files with 1546 additions and 3054 deletions

7
ISSUE_TEMPLATE.md Normal file
View File

@ -0,0 +1,7 @@
**Please read the [wiki](https://github.com/copycat-killer/lain/wiki) and search the [Issues section](https://github.com/copycat-killer/lain/issues) first.**
If you can't find a solution there, then go ahead and provide:
* output of `awesome -v` and `lua -v`
* expected behavior and actual behavior
* steps to reproduce the problem

View File

@ -1,19 +1,24 @@
Lain
====
--------------------------------------------------
Layouts, widgets and utilities for Awesome WM 3.5+
--------------------------------------------------
-------------------------------------------------
Layouts, widgets and utilities for Awesome WM 4.x
-------------------------------------------------
:Author: Luke Bonham <dada [at] archlinux [dot] info>
:Version: git
:License: GNU-GPL2_
:Source: https://github.com/copycat-killer/lain
Warning
-------
If you still have to use branch 3.5.x, you can refer to the commit 301faf5_, but be aware that it's no longer supported.
Description
-----------
Successor of awesome-vain_, this module provides new layouts, a set of widgets and utility functions, in order to improve Awesome_ usability and configurability.
Successor of awesome-vain_, this module provides new layouts, asynchronous widgets and utility functions, with the aim of improving Awesome_ usability and configurability.
Read the wiki_ for all the info.
@ -26,7 +31,7 @@ Just make sure that:
- Your code fits with the general style of the module. In particular, you should use the same indentation pattern that the code uses, and also avoid adding space at the ends of lines.
- Your code its easy to understand, maintainable, and modularized. You should also avoid code duplication wherever possible by adding functions or using ``lain.helpers``. If something is unclear, and you can't write it in such a way that it will be clear, explain it with a comment.
- Your code its easy to understand, maintainable, and modularized. You should also avoid code duplication wherever possible by adding functions or using lain.helpers_. If something is unclear, and you can't write it in such a way that it will be clear, explain it with a comment.
- You test your changes before submitting to make sure that not only your code works, but did not break other parts of the module too!
@ -42,6 +47,8 @@ Screenshots
.. image:: http://i.imgur.com/STCPcaJ.png
.. _GNU-GPL2: http://www.gnu.org/licenses/gpl-2.0.html
.. _301faf5: https://github.com/copycat-killer/lain/tree/301faf5370d045e94c9c344acb0fdac84a2f25a6
.. _awesome-vain: https://github.com/vain/awesome-vain
.. _Awesome: https://github.com/awesomeWM/awesome
.. _wiki: https://github.com/copycat-killer/lain/wiki
.. _lain.helpers: https://github.com/copycat-killer/lain/blob/master/helpers.lua

View File

@ -1,79 +0,0 @@
--[[
Licensed under GNU General Public License v2
* (c) 2015, worron
* (c) 2013, Alexander Yakushev
--]]
-- Asynchronous io.popen for Awesome WM.
-- How to use:
-- asyncshell.request('wscript -Kiev', function(output) wwidget.text = output end)
-- Grab environment
local awful = require('awful')
-- Avoid discrepancies across multiple shells
awful.util.shell = '/bin/sh'
-- Initialize tables for module
asyncshell = { request_table = {}, id_counter = 0 }
-- Request counter
local function next_id()
asyncshell.id_counter = (asyncshell.id_counter + 1) % 10000
return asyncshell.id_counter
end
-- Remove given request
function asyncshell.clear(id)
if asyncshell.request_table[id] then
if asyncshell.request_table[id].timer then
asyncshell.request_table[id].timer:stop()
asyncshell.request_table[id].timer = nil
end
asyncshell.request_table[id] = nil
end
end
-- Sends an asynchronous request for an output of the shell command
-- @param command Command to be executed and taken output from
-- @param callback Function to be called when the command finishes
-- @param timeout Maximum amount of time to wait for the result (optional)
function asyncshell.request(command, callback, timeout)
local id = next_id()
asyncshell.request_table[id] = { callback = callback }
local formatted_command = string.gsub(command, '"','\"')
local req = string.format(
"echo \"asyncshell.deliver(%s, [[\\\"$(%s)\\\"]])\" | awesome-client &",
id, formatted_command
)
if type(awful.spawn) == 'table' and awful.spawn.with_shell then
awful.spawn.with_shell(req)
else
awful.util.spawn_with_shell(req)
end
if timeout then
asyncshell.request_table[id].timer = timer({ timeout = timeout })
asyncshell.request_table[id].timer:connect_signal("timeout", function() asyncshell.clear(id) end)
asyncshell.request_table[id].timer:start()
end
end
-- Calls the remembered callback function on the output of the shell command
-- @param id Request ID
-- @param output Shell command output to be delievered
function asyncshell.deliver(id, output)
local output = string.sub(output, 2, -2)
if asyncshell.request_table[id] then
asyncshell.request_table[id].callback(output)
asyncshell.clear(id)
end
end
return asyncshell

View File

@ -6,15 +6,14 @@
--]]
local debug = require("debug")
local assert = assert
local capi = { timer = (type(timer) == 'table' and timer or require ("gears.timer")) }
local io = { open = io.open,
lines = io.lines,
popen = io.popen }
local rawget = rawget
local table = { sort = table.sort }
local easy_async = require("awful.spawn").easy_async
local timer = require("gears.timer")
local debug = require("debug")
local io = { lines = io.lines,
open = io.open }
local rawget = rawget
local table = { sort = table.sort }
-- Lain helper functions for internal use
-- lain.helpers
@ -90,38 +89,33 @@ end
helpers.timer_table = {}
function helpers.newtimer(_name, timeout, fun, nostart)
local name = timeout
function helpers.newtimer(name, timeout, fun, nostart, stoppable)
if not name or #name == 0 then return end
name = (stoppable and name) or timeout
if not helpers.timer_table[name] then
helpers.timer_table[name] = capi.timer({ timeout = timeout })
helpers.timer_table[name] = timer({ timeout = timeout })
helpers.timer_table[name]:start()
end
helpers.timer_table[name]:connect_signal("timeout", fun)
if not nostart then
helpers.timer_table[name]:emit_signal("timeout")
end
return stoppable and helpers.timer_table[name]
end
-- }}}
-- {{{ Pipe operations
-- read the full output of a command output
function helpers.read_pipe(cmd)
local f = assert(io.popen(cmd))
local output = f:read("*all")
f:close()
return output
end
-- return line iterator of a command output
function helpers.pipelines(...)
local f = assert(io.popen(...))
return function () -- iterator
local data = f:read()
if data == nil then f:close() end
return data
end
-- run a command and execute a function on its output (asynchronous pipe)
-- @param cmd the input command
-- @param callback function to execute on cmd output
-- @return cmd PID
function helpers.async(cmd, callback)
return easy_async(cmd,
function (stdout, stderr, reason, exit_code)
callback(stdout)
end)
end
-- }}}
@ -140,7 +134,19 @@ end
-- }}}
--{{{ Iterate over table of records sorted by keys
-- {{{ Misc
-- check if an element exist on a table
function helpers.element_in_table(element, tbl)
for _, i in pairs(tbl) do
if i == element then
return true
end
end
return false
end
-- iterate over table of records sorted by keys
function helpers.spairs(t)
-- collect the keys
local keys = {}
@ -157,7 +163,7 @@ function helpers.spairs(t)
end
end
end
--}}}
-- }}}
return helpers

View File

Before

Width:  |  Height:  |  Size: 235 B

After

Width:  |  Height:  |  Size: 235 B

View File

Before

Width:  |  Height:  |  Size: 235 B

After

Width:  |  Height:  |  Size: 235 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 247 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 265 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 B

View File

Before

Width:  |  Height:  |  Size: 290 B

After

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 267 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 253 B

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 941 B

View File

@ -9,13 +9,8 @@
--]]
package.loaded.lain = nil
local lain =
{
return {
layout = require("lain.layout"),
util = require("lain.util"),
widgets = require("lain.widgets")
}
return lain

View File

@ -7,19 +7,17 @@ source = {
description = {
summary = "Layout, widgets and utilities for Awesome WM",
detailed = [[
Successor of awesome-vain, this module provides new layouts, a set of widgets and utility functions, in order to improve Awesome usability and configurability.
Successor of awesome-vain, this module provides new layouts, a set of widgets and utility functions, with the aim of improving Awesome usability and configurability.
Optional dependencies: alsa-utils (for alsamixer); curl; imagemagick.
Optional dependency: curl (for IMAP and weather widgets).
]],
homepage = "https://github.com/copycat-killer/lain",
license = "GPL v2"
}
dependencies = {
"lua >= 5.1",
"awesome >= 3.5",
"alsa-utils",
"curl",
"imagemagick"
"lua >= 5.3",
"awesome >= 4.0",
"curl"
}
supported_platforms = { "linux" }
build = {

View File

@ -8,72 +8,166 @@
--]]
local tag = require("awful.tag")
local beautiful = require("beautiful")
local floor = math.floor
local screen = screen
local cascade =
{
local cascade = {
name = "cascade",
nmaster = 0,
offset_x = 32,
offset_y = 8
offset_y = 8,
tile = {
name = "cascadetile",
nmaster = 0,
ncol = 0,
mwfact = 0,
offset_x = 5,
offset_y = 32,
extra_padding = 0
}
}
function cascade.arrange(p)
-- Cascade windows.
-- A global border can be defined with
-- beautiful.global_border_width.
local global_border = tonumber(beautiful.global_border_width) or 0
if global_border < 0 then global_border = 0 end
-- Screen.
local function do_cascade(p, tiling)
local t = p.tag or screen[p.screen].selected_tag
local wa = p.workarea
local cls = p.clients
wa.height = wa.height - (global_border * 2)
wa.width = wa.width - (global_border * 2)
wa.x = wa.x + global_border
wa.y = wa.y + global_border
if #cls == 0 then return end
-- Opening a new window will usually force all existing windows to
-- get resized. This wastes a lot of CPU time. So let's set a lower
-- bound to "how_many": This wastes a little screen space but you'll
-- get a much better user experience.
local t = tag.selected(p.screen)
local num_c
if cascade.nmaster > 0
then
num_c = cascade.nmaster
if not tiling then
-- Cascade windows.
local num_c
if cascade.nmaster > 0 then
num_c = cascade.nmaster
else
num_c = t.master_count
end
-- Opening a new window will usually force all existing windows to
-- get resized. This wastes a lot of CPU time. So let's set a lower
-- bound to "how_many": This wastes a little screen space but you'll
-- get a much better user experience.
local how_many = (#cls >= num_c and #cls) or num_c
local current_offset_x = cascade.offset_x * (how_many - 1)
local current_offset_y = cascade.offset_y * (how_many - 1)
-- Iterate.
for i = 1,#cls,1 do
local c = cls[i]
local g = {}
g.x = wa.x + (how_many - i) * cascade.offset_x
g.y = wa.y + (i - 1) * cascade.offset_y
g.width = wa.width - current_offset_x
g.height = wa.height - current_offset_y
if g.width < 1 then g.width = 1 end
if g.height < 1 then g.height = 1 end
p.geometries[c] = g
end
else
num_c = tag.getnmaster(t)
end
-- Layout with one fixed column meant for a master window. Its
-- width is calculated according to mwfact. Other clients are
-- cascaded or "tabbed" in a slave column on the right.
local how_many = #cls
if how_many < num_c
then
how_many = num_c
end
-- (1) (2) (3) (4)
-- +----------+---+ +----------+---+ +----------+---+ +----------+---+
-- | | | | | 3 | | | 4 | | +---+|
-- | | | -> | | | -> | +---++ -> | +---+|+
-- | 1 | 2 | | 1 +---++ | 1 | 3 || | 1 +---+|+|
-- | | | | | 2 || | +---++| | +---+|+ |
-- | | | | | || | | 2 | | | | 2 |+ |
-- +----------+---+ +---------+---++ +--------+---+-+ +------+---+---+
local current_offset_x = cascade.offset_x * (how_many - 1)
local current_offset_y = cascade.offset_y * (how_many - 1)
local mwfact
if cascade.tile.mwfact > 0 then
mwfact = cascade.tile.mwfact
else
mwfact = t.master_width_factor
end
-- Iterate.
for i = 1,#cls,1
do
local c = cls[i]
-- Make slave windows overlap main window? Do this if ncol is 1.
local overlap_main
if cascade.tile.ncol > 0 then
overlap_main = cascade.tile.ncol
else
overlap_main = t.column_count
end
-- Minimum space for slave windows? See cascade.tile.lua.
local num_c
if cascade.tile.nmaster > 0 then
num_c = cascade.tile.nmaster
else
num_c = t.master_count
end
local how_many = (#cls - 1 >= num_c and (#cls - 1)) or num_c
local current_offset_x = cascade.tile.offset_x * (how_many - 1)
local current_offset_y = cascade.tile.offset_y * (how_many - 1)
if #cls <= 0 then return end
-- Main column, fixed width and height.
local c = cls[1]
local g = {}
-- Rounding is necessary to prevent the rendered size of slavewid
-- from being 1 pixel off when the result is not an integer.
local mainwid = floor(wa.width * mwfact)
local slavewid = wa.width - mainwid
g.x = wa.x + (how_many - i) * cascade.offset_x
g.y = wa.y + (i - 1) * cascade.offset_y
g.width = wa.width - current_offset_x - 2*c.border_width
g.height = wa.height - current_offset_y - 2*c.border_width
if g.width < 1 then g.width = 1 end
if overlap_main == 1 then
g.width = wa.width
-- The size of the main window may be reduced a little bit.
-- This allows you to see if there are any windows below the
-- main window.
-- This only makes sense, though, if the main window is
-- overlapping everything else.
g.width = g.width - cascade.tile.extra_padding
else
g.width = mainwid
end
g.height = wa.height
g.x = wa.x
g.y = wa.y
if g.width < 1 then g.width = 1 end
if g.height < 1 then g.height = 1 end
c:geometry(g)
p.geometries[c] = g
-- Remaining clients stacked in slave column, new ones on top.
if #cls <= 1 then return end
for i = 2,#cls do
c = cls[i]
g = {}
g.width = slavewid - current_offset_x
g.height = wa.height - current_offset_y
g.x = wa.x + mainwid + (how_many - (i - 1)) * cascade.tile.offset_x
g.y = wa.y + (i - 2) * cascade.tile.offset_y
if g.width < 1 then g.width = 1 end
if g.height < 1 then g.height = 1 end
p.geometries[c] = g
end
end
end
function cascade.tile.arrange(p)
return do_cascade(p, true)
end
function cascade.arrange(p)
return do_cascade(p, false)
end
return cascade

View File

@ -1,174 +0,0 @@
--[[
Licensed under GNU General Public License v2
* (c) 2014, projektile
* (c) 2013, Luke Bonham
* (c) 2010-2012, Peter Hofmann
--]]
local tag = require("awful.tag")
local beautiful = require("beautiful")
local tonumber = tonumber
local cascadetile =
{
name = "cascadetile",
nmaster = 0,
ncol = 0,
mwfact = 0,
offset_x = 5,
offset_y = 32,
extra_padding = 0
}
function cascadetile.arrange(p)
-- Layout with one fixed column meant for a master window. Its
-- width is calculated according to mwfact. Other clients are
-- cascaded or "tabbed" in a slave column on the right.
-- It's a bit hard to demonstrate the behaviour with ASCII-images...
--
-- (1) (2) (3) (4)
-- +----------+---+ +----------+---+ +----------+---+ +----------+---+
-- | | | | | 3 | | | 4 | | +---+|
-- | | | -> | | | -> | +---++ -> | +---+|+
-- | 1 | 2 | | 1 +---++ | 1 | 3 || | 1 +---+|+|
-- | | | | | 2 || | +---++| | +---+|+ |
-- | | | | | || | | 2 | | | | 2 |+ |
-- +----------+---+ +---------+---++ +--------+---+-+ +------+---+---+
-- A useless gap (like the dwm patch) can be defined with
-- beautiful.useless_gap_width.
local useless_gap = tonumber(beautiful.useless_gap_width) or 0
if useless_gap < 0 then useless_gap = 0 end
-- A global border can be defined with
-- beautiful.global_border_width
local global_border = tonumber(beautiful.global_border_width) or 0
if global_border < 0 then global_border = 0 end
-- Screen.
local wa = p.workarea
local cls = p.clients
-- Borders are factored in.
wa.height = wa.height - (global_border * 2)
wa.width = wa.width - (global_border * 2)
wa.x = wa.x + global_border
wa.y = wa.y + global_border
-- Width of main column?
local t = tag.selected(p.screen)
local mwfact
if cascadetile.mwfact > 0
then
mwfact = cascadetile.mwfact
else
mwfact = tag.getmwfact(t)
end
-- Make slave windows overlap main window? Do this if ncol is 1.
local overlap_main
if cascadetile.ncol > 0
then
overlap_main = cascadetile.ncol
else
overlap_main = tag.getncol(t)
end
-- Minimum space for slave windows? See cascade.lua.
local num_c
if cascadetile.nmaster > 0
then
num_c = cascadetile.nmaster
else
num_c = tag.getnmaster(t)
end
local how_many = #cls - 1
if how_many < num_c
then
how_many = num_c
end
local current_offset_x = cascadetile.offset_x * (how_many - 1)
local current_offset_y = cascadetile.offset_y * (how_many - 1)
if #cls > 0
then
-- Main column, fixed width and height.
local c = cls[1]
local g = {}
-- Subtracting the useless_gap width from the work area width here
-- makes this mwfact calculation work the same as in uselesstile.
-- Rounding is necessary to prevent the rendered size of slavewid
-- from being 1 pixel off when the result is not an integer.
local mainwid = math.floor((wa.width - useless_gap) * mwfact)
local slavewid = wa.width - mainwid
if overlap_main == 1
then
g.width = wa.width - 2*c.border_width
-- The size of the main window may be reduced a little bit.
-- This allows you to see if there are any windows below the
-- main window.
-- This only makes sense, though, if the main window is
-- overlapping everything else.
g.width = g.width - cascadetile.extra_padding
else
g.width = mainwid - 2*c.border_width
end
g.height = wa.height - 2*c.border_width
g.x = wa.x
g.y = wa.y
if useless_gap > 0
then
-- Reduce width once and move window to the right. Reduce
-- height twice, however.
g.width = g.width - useless_gap
g.height = g.height - 2 * useless_gap
g.x = g.x + useless_gap
g.y = g.y + useless_gap
-- When there's no window to the right, add an additional
-- gap.
if overlap_main == 1
then
g.width = g.width - useless_gap
end
end
if g.width < 1 then g.width = 1 end
if g.height < 1 then g.height = 1 end
c:geometry(g)
-- Remaining clients stacked in slave column, new ones on top.
if #cls > 1
then
for i = 2,#cls
do
c = cls[i]
g = {}
g.width = slavewid - current_offset_x - 2*c.border_width
g.height = wa.height - current_offset_y - 2*c.border_width
g.x = wa.x + mainwid + (how_many - (i - 1)) * cascadetile.offset_x
g.y = wa.y + (i - 2) * cascadetile.offset_y
if useless_gap > 0
then
g.width = g.width - 2 * useless_gap
g.height = g.height - 2 * useless_gap
g.x = g.x + useless_gap
g.y = g.y + useless_gap
end
if g.width < 1 then g.width = 1 end
if g.height < 1 then g.height = 1 end
c:geometry(g)
end
end
end
end
return cascadetile

View File

@ -1,164 +0,0 @@
--[[
Licensed under GNU General Public License v2
* (c) 2014, projektile
* (c) 2013, Luke Bonham
* (c) 2010, Nicolas Estibals
* (c) 2010-2012, Peter Hofmann
--]]
local tag = require("awful.tag")
local beautiful = require("beautiful")
local math = { ceil = math.ceil,
floor = math.floor,
max = math.max }
local tonumber = tonumber
local centerfair = { name = "centerfair" }
function centerfair.arrange(p)
-- Layout with fixed number of vertical columns (read from nmaster).
-- Cols are centerded until there is nmaster columns, then windows
-- are stacked in the slave columns, with at most ncol clients per
-- column if possible.
-- with nmaster=3 and ncol=1 you'll have
-- (1) (2) (3)
-- +---+---+---+ +-+---+---+-+ +---+---+---+
-- | | | | | | | | | | | | |
-- | | 1 | | -> | | 1 | 2 | | -> | 1 | 2 | 3 | ->
-- | | | | | | | | | | | | |
-- +---+---+---+ +-+---+---+-+ +---+---+---+
-- (4) (5)
-- +---+---+---+ +---+---+---+
-- | | | 3 | | | 2 | 4 |
-- + 1 + 2 +---+ -> + 1 +---+---+
-- | | | 4 | | | 3 | 5 |
-- +---+---+---+ +---+---+---+
-- A useless gap (like the dwm patch) can be defined with
-- beautiful.useless_gap_width .
local useless_gap = tonumber(beautiful.useless_gap_width) or 0
if useless_gap < 0 then useless_gap = 0 end
-- A global border can be defined with
-- beautiful.global_border_width
local global_border = tonumber(beautiful.global_border_width) or 0
if global_border < 0 then global_border = 0 end
-- Screen.
local wa = p.workarea
local cls = p.clients
-- Borders are factored in.
wa.height = wa.height - (global_border * 2)
wa.width = wa.width - (global_border * 2)
wa.x = wa.x + global_border
wa.y = wa.y + global_border
-- How many vertical columns? Read from nmaster on the tag.
local t = tag.selected(p.screen)
local num_x = centerfair.nmaster or tag.getnmaster(t)
local ncol = centerfair.ncol or tag.getncol(t)
if num_x <= 2 then num_x = 2 end
local width = math.floor((wa.width - (num_x + 1)*useless_gap) / num_x)
if #cls < num_x
then
-- Less clients than the number of columns, let's center it!
local offset_x = wa.x + (wa.width - #cls*width - (#cls - 1)*useless_gap) / 2
local g = {}
g.y = wa.y + useless_gap
for i = 1, #cls do
local c = cls[i]
g.width = width - 2*c.border_width
g.height = wa.height - 2*useless_gap - 2*c.border_width
if g.width < 1 then g.width = 1 end
if g.height < 1 then g.height = 1 end
g.x = offset_x + (i - 1) * (width + useless_gap)
c:geometry(g)
end
else
-- More clients than the number of columns, let's arrange it!
-- Master client deserves a special treatement
local c = cls[1]
local g = {}
g.width = wa.width - (num_x - 1)*width - (num_x + 1)*useless_gap - 2*c.border_width
g.height = wa.height - 2*useless_gap - 2*c.border_width
if g.width < 1 then g.width = 1 end
if g.height < 1 then g.height = 1 end
g.x = wa.x + useless_gap
g.y = wa.y + useless_gap
c:geometry(g)
-- Treat the other clients
-- Compute distribution of clients among columns
local num_y ={}
do
local remaining_clients = #cls-1
local ncol_min = math.ceil(remaining_clients/(num_x-1))
if ncol >= ncol_min
then
for i = (num_x-1), 1, -1 do
if (remaining_clients-i+1) < ncol
then
num_y[i] = remaining_clients-i + 1
else
num_y[i] = ncol
end
remaining_clients = remaining_clients - num_y[i]
end
else
local rem = remaining_clients % (num_x-1)
if rem ==0
then
for i = 1, num_x-1 do
num_y[i] = ncol_min
end
else
for i = 1, num_x-1 do
num_y[i] = ncol_min - 1
end
for i = 0, rem-1 do
num_y[num_x-1-i] = num_y[num_x-1-i] + 1
end
end
end
end
-- Compute geometry of the other clients
local nclient = 2 -- we start with the 2nd client
g.x = g.x + g.width + useless_gap + 2*c.border_width
for i = 1, (num_x-1) do
local height = math.floor((wa.height - (num_y[i] + 1)*useless_gap) / num_y[i])
g.y = wa.y + useless_gap
for j = 0, (num_y[i]-2) do
local c = cls[nclient]
g.height = height - 2*c.border_width
g.width = width - 2*c.border_width
if g.width < 1 then g.width = 1 end
if g.height < 1 then g.height = 1 end
c:geometry(g)
nclient = nclient + 1
g.y = g.y + height + useless_gap
end
local c = cls[nclient]
g.height = wa.height - (num_y[i] + 1)*useless_gap - (num_y[i] - 1)*height - 2*c.border_width
g.width = width - 2*c.border_width
if g.width < 1 then g.width = 1 end
if g.height < 1 then g.height = 1 end
c:geometry(g)
nclient = nclient + 1
g.x = g.x + width + useless_gap
end
end
end
return centerfair

View File

@ -1,136 +0,0 @@
--[[
Licensed under GNU General Public License v2
* (c) 2015, Joerg Jaspert
* (c) 2014, projektile
* (c) 2013, Luke Bonham
* (c) 2010-2012, Peter Hofmann
--]]
local awful = require("awful")
local beautiful = require("beautiful")
local tonumber = tonumber
local centerhwork =
{
name = "centerhwork",
top_left = 0,
top_right = 1,
bottom_left = 2,
bottom_right = 3
}
function centerhwork.arrange(p)
-- A useless gap (like the dwm patch) can be defined with
-- beautiful.useless_gap_width .
local useless_gap = tonumber(beautiful.useless_gap_width) or 0
-- A global border can be defined with
-- beautiful.global_border_width
local global_border = tonumber(beautiful.global_border_width) or 0
if global_border < 0 then global_border = 0 end
-- Screen.
local wa = p.workarea
local cls = p.clients
-- Borders are factored in.
wa.height = wa.height - (global_border * 2)
wa.width = wa.width - (global_border * 2)
wa.x = wa.x + global_border
wa.y = wa.y + global_border
-- Width of main column?
local t = awful.tag.selected(p.screen)
local mwfact = awful.tag.getmwfact(t)
if #cls > 0
then
-- Main column, fixed width and height.
local c = cls[1]
local g = {}
local mainhei = math.floor(wa.height * mwfact)
local slaveLwid = math.floor(wa.width / 2 )
local slaveRwid = wa.width - slaveLwid
local slavehei = wa.height - mainhei
local slaveThei = math.floor(slavehei / 2)
local slaveBhei = slavehei - slaveThei
local Lhalfgap = math.floor(useless_gap / 2)
local Rhalfgap = useless_gap - Lhalfgap
g.height = mainhei - 2*c.border_width
g.width = wa.width - 2*useless_gap - 2*c.border_width
g.x = wa.x + useless_gap
g.y = wa.y + slaveThei
if g.width < 1 then g.width = 1 end
if g.height < 1 then g.height = 1 end
c:geometry(g)
-- Auxiliary windows.
if #cls > 1
then
local at = 0
for i = 2,#cls
do
-- It's all fixed. If there are more than 5 clients,
-- those additional clients will float. This is
-- intentional.
if at == 4
then
break
end
c = cls[i]
g = {}
if i - 2 == centerhwork.top_left
then
-- top left
g.x = wa.x + useless_gap
g.y = wa.y + useless_gap
g.width = slaveLwid - useless_gap - Lhalfgap - 2*c.border_width
g.height = slaveThei - 2*useless_gap - 2*c.border_width
elseif i - 2 == centerhwork.top_right
then
-- top right
g.x = wa.x + slaveLwid + Rhalfgap
g.y = wa.y + useless_gap
g.width = slaveRwid - useless_gap - Rhalfgap - 2*c.border_width
g.height = slaveThei - 2*useless_gap - 2*c.border_width
elseif i - 2 == centerhwork.bottom_left
then
-- bottom left
g.x = wa.x + useless_gap
g.y = wa.y + mainhei + slaveThei + useless_gap
g.width = slaveLwid - useless_gap - Lhalfgap - 2*c.border_width
g.height = slaveBhei - 2*useless_gap - 2*c.border_width
elseif i - 2 == centerhwork.bottom_right
then
-- bottom right
g.x = wa.x + slaveLwid + Rhalfgap
g.y = wa.y + mainhei + slaveThei + useless_gap
g.width = slaveRwid - useless_gap - Rhalfgap - 2*c.border_width
g.height = slaveBhei - 2*useless_gap - 2*c.border_width
end
if g.width < 1 then g.width = 1 end
if g.height < 1 then g.height = 1 end
c:geometry(g)
at = at + 1
end
-- Set remaining clients to floating.
for i = (#cls - 1 - 4),1,-1
do
c = cls[i]
awful.client.floating.set(c, true)
end
end
end
end
return centerhwork

View File

@ -2,135 +2,153 @@
--[[
Licensed under GNU General Public License v2
* (c) 2016, Henrik Antonsson
* (c) 2015, Joerg Jaspert
* (c) 2014, projektile
* (c) 2013, Luke Bonham
* (c) 2010-2012, Peter Hofmann
--]]
local awful = require("awful")
local beautiful = require("beautiful")
local tonumber = tonumber
local math = { floor = math.floor }
local floor = math.floor
local screen = screen
local centerwork =
{
local centerwork = {
name = "centerwork",
top_right = 0,
bottom_right = 1,
bottom_left = 2,
top_left = 3
horizontal = { name = "centerworkh" }
}
function centerwork.arrange(p)
-- A useless gap (like the dwm patch) can be defined with
-- beautiful.useless_gap_width .
local useless_gap = tonumber(beautiful.useless_gap_width) or 0
-- A global border can be defined with
-- beautiful.global_border_width
local global_border = tonumber(beautiful.global_border_width) or 0
if global_border < 0 then global_border = 0 end
-- Screen.
local function do_centerwork(p, orientation)
local t = p.tag or screen[p.screen].selected_tag
local wa = p.workarea
local cls = p.clients
-- Borders are factored in.
wa.height = wa.height - (global_border * 2)
wa.width = wa.width - (global_border * 2)
wa.x = wa.x + global_border
wa.y = wa.y + global_border
if #cls == 0 then return end
-- Width of main column?
local t = awful.tag.selected(p.screen)
local mwfact = awful.tag.getmwfact(t)
local c = cls[1]
local g = {}
if #cls > 0
then
-- Main column, fixed width and height.
local c = cls[1]
local g = {}
local mainwid = math.floor(wa.width * mwfact)
local slavewid = wa.width - mainwid
local slaveLwid = math.floor(slavewid / 2)
local slaveRwid = slavewid - slaveLwid
local slaveThei = math.floor(wa.height / 2)
local slaveBhei = wa.height - slaveThei
local Thalfgap = math.floor(useless_gap / 2)
local Bhalfgap = useless_gap - Thalfgap
-- Main column, fixed width and height.
local mwfact = t.master_width_factor
local mainhei = floor(wa.height * mwfact)
local mainwid = floor(wa.width * mwfact)
local slavewid = wa.width - mainwid
local slaveLwid = floor(slavewid / 2)
local slaveRwid = slavewid - slaveLwid
local slavehei = wa.height - mainhei
local slaveThei = floor(slavehei / 2)
local slaveBhei = slavehei - slaveThei
local nbrFirstSlaves = floor(#cls / 2)
local nbrSecondSlaves = floor((#cls - 1) / 2)
local slaveFirstDim, slaveSecondDim = 0, 0
if orientation == "vertical" then
if nbrFirstSlaves > 0 then slaveFirstDim = floor(wa.height / nbrFirstSlaves) end
if nbrSecondSlaves > 0 then slaveSecondDim = floor(wa.height / nbrSecondSlaves) end
g.height = wa.height
g.width = mainwid
g.height = wa.height - 2*useless_gap - 2*c.border_width
g.width = mainwid - 2*c.border_width
g.x = wa.x + slaveLwid
g.y = wa.y + useless_gap
g.y = wa.y
else
if nbrFirstSlaves > 0 then slaveFirstDim = floor(wa.width / nbrFirstSlaves) end
if nbrSecondSlaves > 0 then slaveSecondDim = floor(wa.width / nbrSecondSlaves) end
if g.width < 1 then g.width = 1 end
if g.height < 1 then g.height = 1 end
c:geometry(g)
g.height = mainhei
g.width = wa.width
-- Auxiliary windows.
if #cls > 1
then
local at = 0
for i = 2,#cls
do
-- It's all fixed. If there are more than 5 clients,
-- those additional clients will float. This is
-- intentional.
if at == 4
then
break
g.x = wa.x
g.y = wa.y + slaveThei
end
if g.width < 1 then g.width = 1 end
if g.height < 1 then g.height = 1 end
p.geometries[c] = g
-- Auxiliary windows.
if #cls <= 1 then return end
for i = 2,#cls do
local c = cls[i]
local g = {}
local rowIndex = floor(i/2)
if orientation == "vertical" then
if i % 2 == 0 then
-- left slave
g.x = wa.x
g.y = wa.y + (rowIndex-1)*slaveFirstDim
g.width = slaveLwid
-- if last slave in left row use remaining space for that slave
if rowIndex == nbrFirstSlaves then
g.height = wa.y + wa.height - g.y
else
g.height = slaveFirstDim
end
else
-- right slave
g.x = wa.x + slaveLwid + mainwid
g.y = wa.y + (rowIndex-1)*slaveSecondDim
c = cls[i]
g = {}
g.width = slaveRwid
if i - 2 == centerwork.top_left
then
-- top left
g.x = wa.x + useless_gap
g.y = wa.y + useless_gap
g.width = slaveLwid - 2*useless_gap - 2*c.border_width
g.height = slaveThei - useless_gap - Thalfgap - 2*c.border_width
elseif i - 2 == centerwork.top_right
then
-- top right
g.x = wa.x + slaveLwid + mainwid + useless_gap
g.y = wa.y + useless_gap
g.width = slaveRwid - 2*useless_gap - 2*c.border_width
g.height = slaveThei - useless_gap - Thalfgap - 2*c.border_width
elseif i - 2 == centerwork.bottom_left
then
-- bottom left
g.x = wa.x + useless_gap
g.y = wa.y + slaveThei + Bhalfgap
g.width = slaveLwid - 2*useless_gap - 2*c.border_width
g.height = slaveBhei - useless_gap - Bhalfgap - 2*c.border_width
elseif i - 2 == centerwork.bottom_right
then
-- bottom right
g.x = wa.x + slaveLwid + mainwid + useless_gap
g.y = wa.y + slaveThei + Bhalfgap
g.width = slaveRwid - 2*useless_gap - 2*c.border_width
g.height = slaveBhei - useless_gap - Bhalfgap - 2*c.border_width
-- if last slave in right row use remaining space for that slave
if rowIndex == nbrSecondSlaves then
g.height = wa.y + wa.height - g.y
else
g.height = slaveSecondDim
end
if g.width < 1 then g.width = 1 end
if g.height < 1 then g.height = 1 end
c:geometry(g)
at = at + 1
end
else
if i % 2 == 0 then
-- top slave
g.x = wa.x + (rowIndex-1)*slaveFirstDim
g.y = wa.y
g.height = slaveThei
-- if last slave in top row use remaining space for that slave
if rowIndex == nbrFirstSlaves then
g.width = wa.x + wa.width - g.x
else
g.width = slaveFirstDim
end
else
-- bottom slave
g.x = wa.x + (rowIndex-1)*slaveSecondDim
g.y = wa.y + slaveThei + mainhei
g.height = slaveBhei
-- if last slave in bottom row use remaining space for that slave
if rowIndex == nbrSecondSlaves then
g.width = wa.x + wa.width - g.x
else
g.width = slaveSecondDim
end
-- Set remaining clients to floating.
for i = (#cls - 1 - 4),1,-1
do
c = cls[i]
awful.client.floating.set(c, true)
end
end
if g.width < 1 then g.width = 1 end
if g.height < 1 then g.height = 1 end
p.geometries[c] = g
end
end
function centerwork.horizontal.arrange(p)
return do_centerwork(p, "horizontal")
end
function centerwork.arrange(p)
return do_centerwork(p, "vertical")
end
return centerwork

View File

@ -1,123 +0,0 @@
--[[
Licensed under GNU General Public License v2
* (c) 2016, Henrik Antonsson
* (c) 2014, projektile
* (c) 2013, Luke Bonham
* (c) 2010-2012, Peter Hofmann
Based on centerwork.lua
--]]
local awful = require("awful")
local beautiful = require("beautiful")
local tonumber = tonumber
local math = { floor = math.floor }
local centerworkd =
{
name = "centerworkd",
}
function centerworkd.arrange(p)
-- A useless gap (like the dwm patch) can be defined with
-- beautiful.useless_gap_width .
local useless_gap = tonumber(beautiful.useless_gap_width) or 0
-- A global border can be defined with
-- beautiful.global_border_width
local global_border = tonumber(beautiful.global_border_width) or 0
if global_border < 0 then global_border = 0 end
-- Screen.
local wa = p.workarea
local cls = p.clients
-- Borders are factored in.
wa.height = wa.height - (global_border * 2)
wa.width = wa.width - (global_border * 2)
wa.x = wa.x + global_border
wa.y = wa.y + global_border
-- Width of main column?
local t = awful.tag.selected(p.screen)
local mwfact = awful.tag.getmwfact(t)
if #cls > 0
then
-- Main column, fixed width and height.
local c = cls[1]
local g = {}
local mainwid = math.floor(wa.width * mwfact)
local slavewid = wa.width - mainwid
local slaveLwid = math.floor(slavewid / 2)
local slaveRwid = slavewid - slaveLwid
local nbrLeftSlaves = math.floor(#cls / 2)
local nbrRightSlaves = math.floor((#cls - 1) / 2)
local slaveLeftHeight = 0
if nbrLeftSlaves > 0 then slaveLeftHeight = math.floor(wa.height / nbrLeftSlaves) end
if nbrRightSlaves > 0 then slaveRightHeight = math.floor(wa.height / nbrRightSlaves) end
g.height = wa.height - 2*useless_gap - 2*c.border_width
g.width = mainwid - 2*c.border_width
g.x = wa.x + slaveLwid
g.y = wa.y + useless_gap
if g.width < 1 then g.width = 1 end
if g.height < 1 then g.height = 1 end
c:geometry(g)
-- Auxiliary windows.
if #cls > 1
then
for i = 2,#cls
do
c = cls[i]
g = {}
local rowIndex = math.floor(i/2)
-- If i is even it should be placed on the left side
if i % 2 == 0
then
-- left slave
g.x = wa.x + useless_gap
g.y = wa.y + useless_gap + (rowIndex-1)*slaveLeftHeight
g.width = slaveLwid - 2*useless_gap - 2*c.border_width
-- if last slave in left row use remaining space for that slave
if rowIndex == nbrLeftSlaves
then
g.height = wa.y + wa.height - g.y - useless_gap - 2*c.border_width
else
g.height = slaveLeftHeight - useless_gap - 2*c.border_width
end
else
-- right slave
g.x = wa.x + slaveLwid + mainwid + useless_gap
g.y = wa.y + useless_gap + (rowIndex-1)*slaveRightHeight
g.width = slaveRwid - 2*useless_gap - 2*c.border_width
-- if last slave in right row use remaining space for that slave
if rowIndex == nbrRightSlaves
then
g.height = wa.y + wa.height - g.y - useless_gap - 2*c.border_width
else
g.height = slaveRightHeight - useless_gap - 2*c.border_width
end
end
if g.width < 1 then g.width = 1 end
if g.height < 1 then g.height = 1 end
c:geometry(g)
end
end
end
end
return centerworkd

View File

@ -4,136 +4,237 @@
Licensed under GNU General Public License v2
* (c) 2014, projektile
* (c) 2013, Luke Bonham
* (c) 2010, Nicolas Estibals
* (c) 2010-2012, Peter Hofmann
--]]
local tag = require("awful.tag")
local beautiful = require("beautiful")
local math = { ceil = math.ceil,
floor = math.floor,
max = math.max }
local tonumber = tonumber
local math = { ceil = math.ceil,
floor = math.floor,
max = math.max }
local screen = screen
local tonumber = tonumber
local termfair = { name = "termfair" }
termfair.center = { name = "centerfair" }
function termfair.arrange(p)
-- Layout with fixed number of vertical columns (read from nmaster).
-- New windows align from left to right. When a row is full, a now
-- one above it is created. Like this:
-- (1) (2) (3)
-- +---+---+---+ +---+---+---+ +---+---+---+
-- | | | | | | | | | | | |
-- | 1 | | | -> | 2 | 1 | | -> | 3 | 2 | 1 | ->
-- | | | | | | | | | | | |
-- +---+---+---+ +---+---+---+ +---+---+---+
-- (4) (5) (6)
-- +---+---+---+ +---+---+---+ +---+---+---+
-- | 4 | | | | 5 | 4 | | | 6 | 5 | 4 |
-- +---+---+---+ -> +---+---+---+ -> +---+---+---+
-- | 3 | 2 | 1 | | 3 | 2 | 1 | | 3 | 2 | 1 |
-- +---+---+---+ +---+---+---+ +---+---+---+
-- A useless gap (like the dwm patch) can be defined with
-- beautiful.useless_gap_width.
local useless_gap = tonumber(beautiful.useless_gap_width) or 0
if useless_gap < 0 then useless_gap = 0 end
-- A global border can be defined with
-- beautiful.global_border_width
local global_border = tonumber(beautiful.global_border_width) or 0
if global_border < 0 then global_border = 0 end
-- Screen.
local function do_fair(p, orientation)
local t = p.tag or screen[p.screen].selected_tag
local wa = p.workarea
local cls = p.clients
-- Borders are factored in.
wa.height = wa.height - (global_border * 2)
wa.width = wa.width - (global_border * 2)
wa.x = wa.x + global_border
wa.y = wa.y + global_border
if #cls == 0 then return end
-- How many vertical columns?
local t = tag.selected(p.screen)
local num_x = termfair.nmaster or tag.getnmaster(t)
if orientation == "west" then
-- Layout with fixed number of vertical columns (read from nmaster).
-- New windows align from left to right. When a row is full, a now
-- one above it is created. Like this:
-- Do at least "desired_y" rows.
local desired_y = termfair.ncol or tag.getncol(t)
-- (1) (2) (3)
-- +---+---+---+ +---+---+---+ +---+---+---+
-- | | | | | | | | | | | |
-- | 1 | | | -> | 2 | 1 | | -> | 3 | 2 | 1 | ->
-- | | | | | | | | | | | |
-- +---+---+---+ +---+---+---+ +---+---+---+
if #cls > 0
then
local num_y = math.max(math.ceil(#cls / num_x), desired_y)
-- (4) (5) (6)
-- +---+---+---+ +---+---+---+ +---+---+---+
-- | 4 | | | | 5 | 4 | | | 6 | 5 | 4 |
-- +---+---+---+ -> +---+---+---+ -> +---+---+---+
-- | 3 | 2 | 1 | | 3 | 2 | 1 | | 3 | 2 | 1 |
-- +---+---+---+ +---+---+---+ +---+---+---+
-- How many vertical columns? Read from nmaster on the tag.
local num_x = tonumber(termfair.nmaster) or t.master_count
local ncol = tonumber(termfair.ncol) or t.column_count
if num_x <= 2 then num_x = 2 end
if ncol <= 1 then ncol = 1 end
local width = math.floor(wa.width/num_x)
local num_y = math.max(math.ceil(#cls / num_x), ncol)
local height = math.floor(wa.height/num_y)
local cur_num_x = num_x
local at_x = 0
local at_y = 0
local at_x = 0
local at_y = 0
local remaining_clients = #cls
local width = math.floor((wa.width - (num_x + 1)*useless_gap) / num_x)
local height = math.floor((wa.height - (num_y + 1)*useless_gap) / num_y)
-- We start the first row. Left-align by limiting the number of
-- available slots.
if remaining_clients < num_x
then
if remaining_clients < num_x then
cur_num_x = remaining_clients
end
-- Iterate in reversed order.
for i = #cls,1,-1
do
for i = #cls,1,-1 do
-- Get x and y position.
local c = cls[i]
local this_x = cur_num_x - at_x - 1
local this_y = num_y - at_y - 1
-- Calc geometry.
-- Calculate geometry.
local g = {}
if this_x == (num_x - 1)
then
g.width = wa.width - (num_x - 1)*width - (num_x + 1)*useless_gap - 2*c.border_width
if this_x == (num_x - 1) then
g.width = wa.width - (num_x - 1)*width
else
g.width = width - 2*c.border_width
g.width = width
end
if this_y == (num_y - 1)
then
g.height = wa.height - (num_y - 1)*height - (num_y + 1)*useless_gap - 2*c.border_width
if this_y == (num_y - 1) then
g.height = wa.height - (num_y - 1)*height
else
g.height = height - 2*c.border_width
g.height = height
end
g.x = wa.x + this_x*width
g.y = wa.y + this_y*height
if useless_gap > 0
then
-- All clients tile evenly.
g.x = g.x + (this_x + 1)*useless_gap
g.y = g.y + (this_y + 1)*useless_gap
end
if g.width < 1 then g.width = 1 end
if g.width < 1 then g.width = 1 end
if g.height < 1 then g.height = 1 end
c:geometry(g)
p.geometries[c] = g
remaining_clients = remaining_clients - 1
-- Next grid position.
at_x = at_x + 1
if at_x == num_x
then
if at_x == num_x then
-- Row full, create a new one above it.
at_x = 0
at_y = at_y + 1
-- We start a new row. Left-align.
if remaining_clients < num_x
then
if remaining_clients < num_x then
cur_num_x = remaining_clients
end
end
end
elseif orientation == "center" then
-- Layout with fixed number of vertical columns (read from nmaster).
-- Cols are centerded until there is nmaster columns, then windows
-- are stacked in the slave columns, with at most ncol clients per
-- column if possible.
-- with nmaster=3 and ncol=1 you'll have
-- (1) (2) (3)
-- +---+---+---+ +-+---+---+-+ +---+---+---+
-- | | | | | | | | | | | | |
-- | | 1 | | -> | | 1 | 2 | | -> | 1 | 2 | 3 | ->
-- | | | | | | | | | | | | |
-- +---+---+---+ +-+---+---+-+ +---+---+---+
-- (4) (5)
-- +---+---+---+ +---+---+---+
-- | | | 3 | | | 2 | 4 |
-- + 1 + 2 +---+ -> + 1 +---+---+
-- | | | 4 | | | 3 | 5 |
-- +---+---+---+ +---+---+---+
-- How many vertical columns? Read from nmaster on the tag.
local num_x = tonumber(termfair.center.nmaster) or t.master_count
local ncol = tonumber(termfair.center.ncol) or t.column_count
if num_x <= 2 then num_x = 2 end
if ncol <= 1 then ncol = 1 end
local width = math.floor(wa.width / num_x)
if #cls < num_x then
-- Less clients than the number of columns, let's center it!
local offset_x = wa.x + (wa.width - #cls*width) / 2
for i = 1, #cls do
local g = { y = wa.y }
g.width = width
g.height = wa.height
if g.width < 1 then g.width = 1 end
if g.height < 1 then g.height = 1 end
g.x = offset_x + (i - 1) * width
p.geometries[cls[i]] = g
end
else
-- More clients than the number of columns, let's arrange it!
-- Master client deserves a special treatement
local g = {}
g.width = wa.width - (num_x - 1)*width
g.height = wa.height
if g.width < 1 then g.width = 1 end
if g.height < 1 then g.height = 1 end
g.x = wa.x
g.y = wa.y
p.geometries[cls[1]] = g
-- Treat the other clients
-- Compute distribution of clients among columns
local num_y = {}
local remaining_clients = #cls-1
local ncol_min = math.ceil(remaining_clients/(num_x-1))
if ncol >= ncol_min then
for i = (num_x-1), 1, -1 do
if (remaining_clients-i+1) < ncol then
num_y[i] = remaining_clients-i + 1
else
num_y[i] = ncol
end
remaining_clients = remaining_clients - num_y[i]
end
else
local rem = remaining_clients % (num_x-1)
if rem == 0 then
for i = 1, num_x-1 do
num_y[i] = ncol_min
end
else
for i = 1, num_x-1 do
num_y[i] = ncol_min - 1
end
for i = 0, rem-1 do
num_y[num_x-1-i] = num_y[num_x-1-i] + 1
end
end
end
-- Compute geometry of the other clients
local nclient = 2 -- we start with the 2nd client
local wx = g.x + g.width
for i = 1, (num_x-1) do
local height = math.floor(wa.height / num_y[i])
local wy = wa.y
for j = 0, (num_y[i]-2) do
local g = {}
g.x = wx
g.y = wy
g.height = height
g.width = width
if g.width < 1 then g.width = 1 end
if g.height < 1 then g.height = 1 end
p.geometries[cls[nclient]] = g
nclient = nclient + 1
wy = wy + height
end
local g = {}
g.x = wx
g.y = wy
g.height = wa.height - (num_y[i] - 1)*height
g.width = width
if g.width < 1 then g.width = 1 end
if g.height < 1 then g.height = 1 end
p.geometries[cls[nclient]] = g
nclient = nclient + 1
wx = wx + width
end
end
end
end
function termfair.center.arrange(p)
return do_fair(p, "center")
end
function termfair.arrange(p)
return do_fair(p, "west")
end
return termfair

View File

@ -1,108 +0,0 @@
--[[
Licensed under GNU General Public License v2
* (c) 2014, projektile, worron
* (c) 2013, Luke Bonham
* (c) 2012, Josh Komoroske
* (c) 2010-2012, Peter Hofmann
--]]
local beautiful = require("beautiful")
local ipairs = ipairs
local math = { ceil = math.ceil, sqrt = math.sqrt, floor = math.floor, max = math.max }
local tonumber = tonumber
local uselessfair = {}
-- Transformation functions
local function swap(geometry)
return { x = geometry.y, y = geometry.x, width = geometry.height, height = geometry.width }
end
-- Client geometry correction depending on useless gap and window border
local function size_correction(c, geometry, useless_gap)
geometry.width = math.max(geometry.width - 2 * c.border_width - useless_gap, 1)
geometry.height = math.max(geometry.height - 2 * c.border_width - useless_gap, 1)
geometry.x = geometry.x + useless_gap / 2
geometry.y = geometry.y + useless_gap / 2
end
-- Main tiling function
local function fair(p, orientation)
-- Theme vars
local useless_gap = beautiful.useless_gap_width or 0
local global_border = beautiful.global_border_width or 0
-- Aliases
local wa = p.workarea
local cls = p.clients
-- Nothing to tile here
if #cls == 0 then return end
-- Workarea size correction depending on useless gap and global border
wa.height = wa.height - 2 * global_border - useless_gap
wa.width = wa.width - 2 * global_border - useless_gap
wa.x = wa.x + useless_gap / 2 + global_border
wa.y = wa.y + useless_gap / 2 + global_border
-- Geometry calculation
local row, col = 0, 0
local rows = math.ceil(math.sqrt(#cls))
local cols = math.ceil(#cls / rows)
for i, c in ipairs(cls) do
local g = {}
-- find tile orientation for current client and swap geometry if need
local need_swap = (orientation == "east" and #cls <= 2) or (orientation == "south" and #cls > 2)
local area = need_swap and swap(wa) or wa
-- calculate geometry
if #cls < (cols * rows) and row == cols - 1 then
g.width = area.width / (rows - ((cols * rows) - #cls))
else
g.width = area.width / rows
end
g.height = area.height / cols
g.x = area.x + col * g.width
g.y = area.y + row * g.height
-- turn back to real if geometry was swapped
if need_swap then g = swap(g) end
-- window size correction depending on useless gap and window border
size_correction(c, g, useless_gap)
-- set geometry
c:geometry(g)
-- update tile grid coordinates
col = i % rows
row = math.floor(i / rows)
end
end
-- Layout constructor
local function construct_layout(name, direction)
return {
name = name,
-- @p screen The screen number to tile
arrange = function(p) return fair(p, direction) end
}
end
-- Build layouts with different tile direction
uselessfair.vertical = construct_layout("uselessfair", "south")
uselessfair.horizontal = construct_layout("uselessfairh", "east")
-- Module aliase
uselessfair.arrange = uselessfair.vertical.arrange
uselessfair.name = uselessfair.vertical.name
return uselessfair

View File

@ -1,123 +0,0 @@
--[[
Licensed under GNU General Public License v2
* (c) 2014, projektile
* (c) 2013, Luke Bonham
* (c) 2009, Uli Schlachter
* (c) 2008, Julien Danjolu
--]]
local beautiful = require("beautiful")
local ipairs = ipairs
local tonumber = tonumber
local math = require("math")
local uselesspiral = {}
local function spiral(p, spiral)
-- A useless gap (like the dwm patch) can be defined with
-- beautiful.useless_gap_width.
local useless_gap = tonumber(beautiful.useless_gap_width) or 0
if useless_gap < 0 then useless_gap = 0 end
-- A global border can be defined with
-- beautiful.global_border_width
local global_border = tonumber(beautiful.global_border_width) or 0
if global_border < 0 then global_border = 0 end
-- Themes border width requires an offset
local bw = tonumber(beautiful.border_width) or 0
-- get our orientation right
local wa = p.workarea
local cls = p.clients
local n = #cls -- number of windows total; k = which window number
wa.height = wa.height - ((global_border * 2) + (bw * 2))
wa.width = wa.width - ((global_border * 2) + (bw * 2))
local static_wa = wa
for k, c in ipairs(cls) do
if k < n then
if k % 2 == 0 then
wa.height = (wa.height / 2)
else
wa.width = (wa.width / 2)
end
end
if k % 4 == 0 and spiral then
wa.x = wa.x - wa.width
elseif k % 2 == 0 or
(k % 4 == 3 and k < n and spiral) then
wa.x = wa.x + wa.width
end
if k % 4 == 1 and k ~= 1 and spiral then
wa.y = wa.y - wa.height
elseif k % 2 == 1 and k ~= 1 or
(k % 4 == 0 and k < n and spiral) then
wa.y = wa.y + wa.height
end
local wa2 = {}
wa2.x = wa.x + (useless_gap / 2) + global_border
wa2.y = wa.y + (useless_gap / 2) + global_border
wa2.height = wa.height - (useless_gap / 2)
wa2.width = wa.width - (useless_gap / 2)
-- Useless gap.
if useless_gap > 0
then
-- Top and left clients are shrinked by two steps and
-- get moved away from the border. Other clients just
-- get shrinked in one direction.
top = false
left = false
if wa2.y == static_wa.y then
top = true
end
if wa2.x == static_wa.x then
left = true
end
if top then
wa2.height = wa2.height - useless_gap
wa2.y = wa2.y - (useless_gap / 2)
else
wa2.height = wa2.height - (useless_gap / 2)
end
if left then
wa2.width = wa2.width - useless_gap
wa2.x = wa2.x - (useless_gap / 2)
else
wa2.width = wa2.width - (useless_gap / 2)
end
end
-- End of useless gap.
c:geometry(wa2)
end
end
--- Dwindle layout
uselesspiral.dwindle = {}
uselesspiral.dwindle.name = "uselessdwindle"
function uselesspiral.dwindle.arrange(p)
return spiral(p, false)
end
--- Spiral layout
uselesspiral.name = "uselesspiral"
function uselesspiral.arrange(p)
return spiral(p, true)
end
return uselesspiral

View File

@ -1,231 +0,0 @@
--[[
Licensed under GNU General Public License v2
* (c) 2014, projektile, worron
* (c) 2013, Luke Bonham
* (c) 2009, Donald Ephraim Curtis
* (c) 2008, Julien Danjolu
--]]
local tag = require("awful.tag")
local beautiful = require("beautiful")
local ipairs = ipairs
local math = { floor = math.floor,
ceil = math.ceil,
max = math.max,
min = math.min }
local tonumber = tonumber
local uselesstile = {}
-- Transformation functions
local function flip(canvas, geometry)
return {
-- vertical only
x = 2 * canvas.x + canvas.width - geometry.x - geometry.width,
y = geometry.y,
width = geometry.width,
height = geometry.height
}
end
local function swap(geometry)
return { x = geometry.y, y = geometry.x, width = geometry.height, height = geometry.width }
end
-- Find geometry for secondary windows column
local function cut_column(wa, n, index)
local width = math.floor(wa.width / n)
local area = { x = wa.x + (index - 1) * width, y = wa.y, width = width, height = wa.height }
return area
end
-- Find geometry for certain window in column
local function cut_row(wa, factor, index, used)
local height = math.floor(wa.height * factor.window[index] / factor.total)
local area = { x = wa.x, y = wa.y + used, width = wa.width, height = height }
return area
end
-- Client geometry correction depending on useless gap and window border
local function size_correction(c, geometry, useless_gap)
geometry.width = math.max(geometry.width - 2 * c.border_width - useless_gap, 1)
geometry.height = math.max(geometry.height - 2 * c.border_width - useless_gap, 1)
geometry.x = geometry.x + useless_gap / 2
geometry.y = geometry.y + useless_gap / 2
end
-- Check size factor for group of clients and calculate total
local function calc_factor(n, winfactors)
local factor = { window = winfactors, total = 0, min = 1 }
for i = 1, n do
if not factor.window[i] then
factor.window[i] = factor.min
else
factor.min = math.min(factor.window[i], factor.min)
if factor.window[i] < 0.05 then factor.window[i] = 0.05 end
end
factor.total = factor.total + factor.window[i]
end
return factor
end
-- Tile group of clients in given area
-- @canvas need for proper transformation only
-- @winfactors table with clients size factors
local function tile_column(canvas, area, list, useless_gap, transformation, winfactors)
local used = 0
local factor = calc_factor(#list, winfactors)
for i, c in ipairs(list) do
local g = cut_row(area, factor, i, used)
if i == #list then g.height = area.height - used end
used = used + g.height
-- swap workarea dimensions
if transformation.flip then g = flip(canvas, g) end
if transformation.swap then g = swap(g) end
-- useless gap and border correction
size_correction(c, g, useless_gap)
c:geometry(g)
end
end
--Main tile function
local function tile(p, orientation)
-- Theme vars
local useless_gap = beautiful.useless_gap_width or 0
local global_border = beautiful.global_border_width or 0
-- Aliases
local wa = p.workarea
local cls = p.clients
local t = tag.selected(p.screen)
-- Nothing to tile here
if #cls == 0 then return end
-- Get tag prop
local nmaster = math.min(tag.getnmaster(t), #cls)
local mwfact = tag.getmwfact(t)
if nmaster == 0 then
mwfact = 0
elseif nmaster == #cls then
mwfact = 1
end
-- clients size factor
local data = tag.getdata(t).windowfact
if not data then
data = {}
tag.getdata(t).windowfact = data
end
-- Workarea size correction depending on useless gap and global border
wa.height = wa.height - 2 * global_border - useless_gap
wa.width = wa.width - 2 * global_border - useless_gap
wa.x = wa.x + useless_gap / 2 + global_border
wa.y = wa.y + useless_gap / 2 + global_border
-- Find which transformation we need for given orientation
local transformation = {
swap = orientation == 'top' or orientation == 'bottom',
flip = orientation == 'left' or orientation == 'top'
}
-- Swap workarea dimensions if orientation vertical
if transformation.swap then wa = swap(wa) end
-- Split master and other windows
local cls_master, cls_other = {}, {}
for i, c in ipairs(cls) do
if i <= nmaster then
table.insert(cls_master, c)
else
table.insert(cls_other, c)
end
end
-- Tile master windows
local master_area = {
x = wa.x,
y = wa.y,
width = nmaster > 0 and math.floor(wa.width * mwfact) or 0,
height = wa.height
}
if not data[0] then data[0] = {} end
tile_column(wa, master_area, cls_master, useless_gap, transformation, data[0])
-- Tile other windows
local other_area = {
x = wa.x + master_area.width,
y = wa.y,
width = wa.width - master_area.width,
height = wa.height
}
-- get column number for other windows
local ncol = math.min(tag.getncol(t), #cls_other)
if ncol == 0 then ncol = 1 end
-- split other windows to column groups
local last_small_column = ncol - #cls_other % ncol
local rows_min = math.floor(#cls_other / ncol)
local client_index = 1
local used = 0
for i = 1, ncol do
local position = transformation.flip and ncol - i + 1 or i
local rows = i <= last_small_column and rows_min or rows_min + 1
local column = {}
for j = 1, rows do
table.insert(column, cls_other[client_index])
client_index = client_index + 1
end
-- and tile
local column_area = cut_column(other_area, ncol, position)
if i == ncol then column_area.width = other_area.width - used end
used = used + column_area.width
if not data[i] then data[i] = {} end
tile_column(wa, column_area, column, useless_gap, transformation, data[i])
end
end
-- Layout constructor
local function construct_layout(name, orientation)
return {
name = name,
-- @p screen number to tile
arrange = function(p) return tile(p, orientation) end
}
end
-- Build layouts with different tile direction
uselesstile.right = construct_layout("uselesstile", "right")
uselesstile.left = construct_layout("uselesstileleft", "left")
uselesstile.bottom = construct_layout("uselesstilebottom", "bottom")
uselesstile.top = construct_layout("uselesstiletop", "top")
-- Module aliase
uselesstile.arrange = uselesstile.right.arrange
uselesstile.name = uselesstile.right.name
return uselesstile

View File

@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env sh
#
# Adapted from Eridan's "fs" (cleanup, enhancements and switch to bash/Linux)
# JM, 10/12/2004

View File

@ -1,68 +0,0 @@
#!/bin/bash
#
# A simple cover fetcher script for current playing song on mpd.
#
# Original author: Wolfgang Mueller
#
# Adapted for Lain internal use.
# https://github.com/copycat-killer/lain
#
# You can use, edit and redistribute this script in any way you like.
#
# Dependencies: imagemagick.
#
# Usage: mpdcover <music_directory> <song_file> <cover_resize> <default_art>
# Configuration-------------------------------------------------------
# Music directory
MUSIC_DIR=$1
# Song file
file=$2
# Regex expression used for image search
IMG_REG="(Front|front|Cover|cover|Art|art|Folder|folder)\.(jpg|jpeg|png|gif)$"
# Path of temporary resized cover
TEMP_PATH="/tmp/mpdcover.png"
# Resize cover
COVER_RESIZE="$3x$3"
if [ $COVER_RESIZE == "x" ]; then
COVER_RESIZE="100x100"
fi
# The default cover to use (optional)
DEFAULT_ART=$4
# Thumbnail background (transparent)
COVER_BACKGROUND="none"
#--------------------------------------------------------------------
# check if anything is playing at all
[[ -z $file ]] && exit 1
# Art directory
art="$MUSIC_DIR/${file%/*}"
# find every file that matches IMG_REG set the first matching file to be the
# cover.
cover="$(find "$art/" -maxdepth 1 -type f | egrep -i -m1 "$IMG_REG")"
# when no cover is found, use DEFAULT_ART as cover
cover="${cover:=$DEFAULT_ART}"
# check if art is available
if [[ -n $cover ]]; then
if [[ -n $COVER_RESIZE ]]; then
convert "$cover" -scale $COVER_RESIZE -gravity "center" -background "$COVER_BACKGROUND" "$TEMP_PATH"
cover="$TEMP_PATH"
fi
else
rm $TEMP_PATH
fi
exit 0

View File

@ -13,15 +13,10 @@
--]]
local awful = require("awful")
local beautiful = require("beautiful")
local math = { sqrt = math.sqrt }
local mouse = mouse
local sqrt = math.sqrt
local pairs = pairs
local string = { gsub = string.gsub }
local client = client
local screen = screen
local tonumber = tonumber
local wrequire = require("lain.helpers").wrequire
local setmetatable = setmetatable
@ -29,26 +24,23 @@ local setmetatable = setmetatable
-- lain.util
local util = { _NAME = "lain.util" }
-- Like awful.menu.clients, but only show clients of currently selected
-- tags.
-- Like awful.menu.clients, but only show clients of currently selected tags
function util.menu_clients_current_tags(menu, args)
-- List of currently selected tags.
local cls_tags = awful.tag.selectedlist(mouse.screen)
local cls_tags = awful.screen.focused().selected_tags
if cls_tags == nil then return nil end
-- Final list of menu items.
local cls_t = {}
if cls_tags == nil then return nil end
-- For each selected tag get all clients of that tag and add them to
-- the menu. A click on a menu item will raise that client.
for i = 1,#cls_tags
do
local t = cls_tags[i]
for i = 1,#cls_tags do
local t = cls_tags[i]
local cls = t:clients()
for k, c in pairs(cls)
do
for k, c in pairs(cls) do
cls_t[#cls_t + 1] = { awful.util.escape(c.name) or "",
function ()
c.minimized = false
@ -68,167 +60,109 @@ function util.menu_clients_current_tags(menu, args)
-- Set the list of items and show the menu.
menu.items = cls_t
local m = awful.menu.new(menu)
local m = awful.menu(menu)
m:show(args)
return m
end
-- Magnify a client: Set it to "float" and resize it.
local magnified_client = nil
-- Magnify a client: set it to "float" and resize it.
function util.magnify_client(c)
if c and not awful.client.floating.get(c) then
if c and not c.floating then
util.mc(c)
magnified_client = c
util.magnified_client = c
else
awful.client.floating.set(c, false)
magnified_client = nil
c.floating = false
util.magnified_client = nil
end
end
-- https://github.com/copycat-killer/lain/issues/195
function util.mc(c)
c = c or magnified_client
c = c or util.magnified_client
if not c then return end
awful.client.floating.set(c, true)
local mg = screen[mouse.screen].geometry
local tag = awful.tag.selected(mouse.screen)
local mwfact = awful.tag.getmwfact(tag)
local g = {}
g.width = math.sqrt(mwfact) * mg.width
g.height = math.sqrt(mwfact) * mg.height
g.x = mg.x + (mg.width - g.width) / 2
g.y = mg.y + (mg.height - g.height) / 2
c.floating = true
local s = awful.screen.focused()
local mg = s.geometry
local mwfact = s.selected_tag.master_width_factor or 0.5
local g = {}
g.width = sqrt(mwfact) * mg.width
g.height = sqrt(mwfact) * mg.height
g.x = mg.x + (mg.width - g.width) / 2
g.y = mg.y + (mg.height - g.height) / 2
if c then c:geometry(g) end -- if c is still a valid object
end
-- Read the nice value of pid from /proc.
local function get_nice_value(pid)
local n = first_line('/proc/' .. pid .. '/stat')
if not n then return 0 end
-- Remove pid and tcomm. This is necessary because tcomm may contain
-- nasty stuff such as whitespace or additional parentheses...
n = string.gsub(n, '.*%) ', '')
-- Field number 17 now is the nice value.
fields = split(n, ' ')
return tonumber(fields[17])
end
-- To be used as a signal handler for "focus"
-- This requires beautiful.border_focus{,_highprio,_lowprio}.
function util.niceborder_focus(c)
local n = get_nice_value(c.pid)
if n == 0
then
c.border_color = beautiful.border_focus
elseif n < 0
then
c.border_color = beautiful.border_focus_highprio
else
c.border_color = beautiful.border_focus_lowprio
end
end
-- To be used as a signal handler for "unfocus"
-- This requires beautiful.border_normal{,_highprio,_lowprio}.
function util.niceborder_unfocus(c)
local n = get_nice_value(c.pid)
if n == 0
then
c.border_color = beautiful.border_normal
elseif n < 0
then
c.border_color = beautiful.border_normal_highprio
else
c.border_color = beautiful.border_normal_lowprio
end
end
-- Non-empty tag browsing
-- direction in {-1, 1} <-> {previous, next} non-empty tag
function util.tag_view_nonempty(direction, sc)
local s = sc or mouse.screen or 1
local scr = screen[s]
local s = sc or awful.screen.focused()
for i = 1, #awful.tag.gettags(s) do
awful.tag.viewidx(direction,s)
if #awful.client.visible(s) > 0 then
for i = 1, #s.tags do
awful.tag.viewidx(direction, s)
if #s.clients > 0 then
return
end
end
end
-- {{{ Dynamic tagging
--
-- Add a new tag
function util.add_tag(mypromptbox)
awful.prompt.run({prompt="New tag name: "}, mypromptbox[mouse.screen].widget,
function(text)
if text:len() > 0 then
props = { selected = true }
tag = awful.tag.add(new_name, props)
tag.name = text
tag:emit_signal("property::name")
function util.add_tag()
awful.prompt.run {
prompt = "New tag name: ",
textbox = awful.screen.focused().mypromptbox.widget,
exe_callback = function(name)
if not name or #name == 0 then return end
awful.tag.add(name, { screen = awful.screen.focused() }):view_only()
end
end)
}
end
-- Rename current tag
-- @author: minism
function util.rename_tag(mypromptbox)
local tag = awful.tag.selected(mouse.screen)
awful.prompt.run({prompt="Rename tag: "}, mypromptbox[mouse.screen].widget,
function(text)
if text:len() > 0 then
tag.name = text
tag:emit_signal("property::name")
function util.rename_tag()
awful.prompt.run {
prompt = "Rename tag: ",
textbox = awful.screen.focused().mypromptbox.widget,
exe_callback = function(new_name)
if not new_name or #new_name == 0 then return end
local t = awful.screen.focused().selected_tag
if t then
t.name = new_name
end
end
end)
}
end
-- Move current tag
-- pos in {-1, 1} <-> {previous, next} tag position
function util.move_tag(pos)
local tag = awful.tag.selected(mouse.screen)
local idx = awful.tag.getidx(tag)
local tag = awful.screen.focused().selected_tag
if tonumber(pos) <= -1 then
awful.tag.move(idx - 1, tag)
awful.tag.move(tag.index - 1, tag)
else
awful.tag.move(idx + 1, tag)
awful.tag.move(tag.index + 1, tag)
end
end
-- Remove current tag (if empty)
-- Delete current tag
-- Any rule set on the tag shall be broken
function util.remove_tag()
local tag = awful.tag.selected(mouse.screen)
local prevtag = awful.tag.gettags(mouse.screen)[awful.tag.getidx(tag) - 1]
awful.tag.delete(tag, prevtag)
function util.delete_tag()
local t = awful.screen.focused().selected_tag
if not t then return end
t:delete()
end
--
-- }}}
-- On the fly useless gaps change
function util.useless_gaps_resize(thatmuch)
beautiful.useless_gap_width = tonumber(beautiful.useless_gap_width) + thatmuch
awful.layout.arrange(mouse.screen)
end
-- On the fly global border change
function util.global_border_resize(thatmuch)
beautiful.global_border_width = tonumber(beautiful.global_border_width) + thatmuch
awful.layout.arrange(mouse.screen)
end
-- Check if an element exist on a table
function util.element_in_table(element, tbl)
for _, i in pairs(tbl) do
if i == element then
return true
end
end
return false
local scr = awful.screen.focused()
scr.selected_tag.gap = scr.selected_tag.gap + tonumber(thatmuch)
awful.layout.arrange(scr)
end
return setmetatable(util, { __index = wrequire })

View File

@ -8,58 +8,56 @@
--]]
local beautiful = require("beautiful")
local tostring = tostring
local string = { format = string.format }
local setmetatable = setmetatable
-- Lain markup util submodule
-- lain.util.markup
local markup = {}
local fg = {}
local bg = {}
local markup = { fg = {}, bg = {} }
-- Convenience tags.
function markup.bold(text) return '<b>' .. tostring(text) .. '</b>' end
function markup.italic(text) return '<i>' .. tostring(text) .. '</i>' end
function markup.strike(text) return '<s>' .. tostring(text) .. '</s>' end
function markup.underline(text) return '<u>' .. tostring(text) .. '</u>' end
function markup.monospace(text) return '<tt>' .. tostring(text) .. '</tt>' end
function markup.big(text) return '<big>' .. tostring(text) .. '</big>' end
function markup.small(text) return '<small>' .. tostring(text) .. '</small>' end
function markup.bold(text) return '<b>' .. text .. '</b>' end
function markup.italic(text) return '<i>' .. text .. '</i>' end
function markup.strike(text) return '<s>' .. text .. '</s>' end
function markup.underline(text) return '<u>' .. text .. '</u>' end
function markup.monospace(text) return '<tt>' .. text .. '</tt>' end
function markup.big(text) return '<big>' .. text .. '</big>' end
function markup.small(text) return '<small>' .. text .. '</small>' end
-- Set the font.
function markup.font(font, text)
return '<span font="' .. tostring(font) .. '">' .. tostring(text) ..'</span>'
return '<span font="' .. font .. '">' .. text ..'</span>'
end
-- Set the foreground.
function fg.color(color, text)
return '<span foreground="' .. tostring(color) .. '">' .. tostring(text) .. '</span>'
function markup.fg.color(color, text)
return '<span foreground="' .. color .. '">' .. text .. '</span>'
end
-- Set the background.
function bg.color(color, text)
return '<span background="' .. tostring(color) .. '">' .. tostring(text) .. '</span>'
function markup.bg.color(color, text)
return '<span background="' .. color .. '">' .. text .. '</span>'
end
-- Context: focus
function fg.focus(text) return fg.color(beautiful.fg_focus, text) end
function bg.focus(text) return bg.color(beautiful.bg_focus, text) end
function markup.focus(text) return bg.focus(fg.focus(text)) end
-- Set foreground and background.
function markup.color(fg, bg, text)
return string.format('<span foreground="%s" background="%s">%s</span>', fg, bg, text)
end
-- Context: normal
function fg.normal(text) return fg.color(beautiful.fg_normal, text) end
function bg.normal(text) return bg.color(beautiful.bg_normal, text) end
function markup.normal(text) return bg.normal(fg.normal(text)) end
-- Set font and foreground.
function markup.fontfg(font, fg, text)
return string.format('<span font="%s" foreground="%s">%s</span>', font, fg, text)
end
-- Context: urgent
function fg.urgent(text) return fg.color(beautiful.fg_urgent, text) end
function bg.urgent(text) return bg.color(beautiful.bg_urgent, text) end
function markup.urgent(text) return bg.urgent(fg.urgent(text)) end
-- Set font and background.
function markup.fontbg(font, bg, text)
return string.format('<span font="%s" background="%s">%s</span>', font, bg, text)
end
markup.fg = fg
markup.bg = bg
-- Set font, foreground and background.
function markup.fontcolor(font, fg, bg, text)
return string.format('<span font="%s" foreground="%s" background="%s">%s</span>', font, fg, bg, text)
end
-- link markup.{fg,bg}(...) calls to markup.{fg,bg}.color(...)
setmetatable(markup.fg, { __call = function(_, ...) return markup.fg.color(...) end })

View File

@ -3,22 +3,22 @@
Licensed under GNU General Public License v2
* (c) 2016, Luke Bonham
* (c) 2015, unknown
--]]
local awful = require("awful")
local capi = { client = client,
mouse = mouse,
screen = screen,
timer = timer }
local string = string
local awful = require("awful")
local capi = { client = client }
local math = { floor = math.floor }
local string = { format = string.format }
local pairs = pairs
local screen = screen
local setmetatable = setmetatable
local tostring = tostring
-- Quake-like Dropdown application spawn
-- Original version: https://awesomewm.org/wiki/Drop-down_terminal#Another_solution
local quake = {}
-- If you have a rule like "awful.client.setslave" for your terminals,
@ -26,137 +26,144 @@ local quake = {}
-- run into problems with focus.
function quake:display()
-- First, we locate the client
local client = nil
local i = 0
for c in awful.client.iterate(function (c)
-- c.name may be changed!
return c.instance == self.name
end, nil, self.screen)
do
i = i + 1
if i == 1 then
client = c
else
-- Additional matching clients, let's remove the sticky bit
-- which may persist between awesome restarts. We don't close
-- them as they may be valuable. They will just turn into
-- normal clients.
c.sticky = false
c.ontop = false
c.above = false
end
end
if self.followtag then self.screen = awful.screen.focused() end
if not client and not self.visible then return end
-- First, we locate the client
local client = nil
local i = 0
for c in awful.client.iterate(function (c)
-- c.name may be changed!
return c.instance == self.name
end, nil, self.screen)
do
i = i + 1
if i == 1 then
client = c
else
-- Additional matching clients, let's remove the sticky bit
-- which may persist between awesome restarts. We don't close
-- them as they may be valuable. They will just turn into
-- normal clients.
c.sticky = false
c.ontop = false
c.above = false
end
end
if not client then
-- The client does not exist, we spawn it
awful.util.spawn(string.format("%s %s %s", self.app,
string.format(self.argname, self.name), self.extra),
false, self.screen)
self.notexist = true
return
end
if not client and not self.visible then return end
-- Resize
awful.client.floating.set(client, true)
client.border_width = self.border
client.size_hints_honor = false
if self.notexist then
client:geometry(self.geometry)
self.notexist = false
end
if not client then
-- The client does not exist, we spawn it
cmd = string.format("%s %s %s", self.app,
string.format(self.argname, self.name), self.extra)
awful.spawn(cmd, { tag = self.screen.selected_tag })
return
end
-- Not sticky and on top
client.ontop = true
client.above = true
client.skip_taskbar = true
client.sticky = false
-- Set geometry
client.floating = true
client.border_width = self.border
client.size_hints_honor = false
client:geometry(self:compute_size())
-- Toggle display
if self.visible then
client.hidden = false
client:raise()
self.last_tag = tostring(awful.tag.selected(self.screen))
client:tags({awful.tag.selected(self.screen)})
capi.client.focus = client
-- Set not sticky and on top
client.sticky = false
client.ontop = true
client.above = true
client.skip_taskbar = true
-- Additional user settings
if self.settings then self.settings(client) end
-- Toggle display
if self.visible then
client.hidden = false
client:raise()
self.last_tag = self.screen.selected_tag
client:tags({self.screen.selected_tag})
capi.client.focus = client
else
client.hidden = true
local ctags = client:tags()
for i, t in pairs(ctags) do
ctags[i] = nil
end
client:tags(ctags)
end
client.hidden = true
local ctags = client:tags()
for i, t in pairs(ctags) do
ctags[i] = nil
end
client:tags(ctags)
end
return client
return client
end
function quake:compute_size()
-- skip if we already have a geometry for this screen
if not self.geometry[self.screen] then
local geom
if not self.overlap then
geom = screen[self.screen].workarea
else
geom = screen[self.screen].geometry
end
local width, height = self.width, self.height
if width <= 1 then width = math.floor(geom.width * width) - 2 * self.border end
if height <= 1 then height = math.floor(geom.height * height) end
local x, y
if self.horiz == "left" then x = geom.x
elseif self.horiz == "right" then x = geom.width + geom.x - width
else x = geom.x + (geom.width - width)/2 end
if self.vert == "top" then y = geom.y
elseif self.vert == "bottom" then y = geom.height + geom.y - height
else y = geom.y + (geom.height - height)/2 end
self.geometry[self.screen] = { x = x, y = y, width = width, height = height }
end
return self.geometry[self.screen]
end
function quake:new(config)
local conf = config or {}
local conf = config or {}
conf.app = conf.app or "xterm" -- application to spawn
conf.name = conf.name or "QuakeDD" -- window name
conf.argname = conf.argname or "-name %s" -- how to specify window name
conf.extra = conf.extra or "" -- extra arguments
conf.visible = conf.visible or false -- initially not visible
conf.screen = conf.screen or capi.mouse.screen
conf.border = conf.border or 1
conf.app = conf.app or "xterm" -- application to spawn
conf.name = conf.name or "QuakeDD" -- window name
conf.argname = conf.argname or "-name %s" -- how to specify window name
conf.extra = conf.extra or "" -- extra arguments
conf.border = conf.border or 1 -- client border width
conf.visible = conf.visible or false -- initially not visible
conf.followtag = conf.followtag or false -- spawn on currently focused screen
conf.overlap = conf.overlap or false -- overlap wibox
conf.screen = conf.screen or awful.screen.focused()
conf.settings = conf.settings
-- If width or height <= 1 this is a proportion of the workspace
wibox_height = conf.wibox_height or 18 -- statusbar weight
height = conf.height or 0.25 -- height
width = conf.width or 1 -- width
vert = conf.vert or "top" -- top, bottom or center
horiz = conf.horiz or "center" -- left, right or center
-- If width or height <= 1 this is a proportion of the workspace
conf.height = conf.height or 0.25 -- height
conf.width = conf.width or 1 -- width
conf.vert = conf.vert or "top" -- top, bottom or center
conf.horiz = conf.horiz or "left" -- left, right or center
conf.geometry = {} -- internal use
-- Compute size
local geom = capi.screen[conf.screen].workarea
if width <= 1 then width = geom.width * width end
if height <= 1 then height = geom.height * height end
local x, y
if horiz == "left" then x = geom.x
elseif horiz == "right" then x = geom.width + geom.x - width
else x = geom.x + (geom.width - width)/2 end
if vert == "top" then y = geom.y
elseif vert == "bottom" then y = geom.height + geom.y - height
else y = geom.y + (geom.height - height)/2 end
conf.geometry = { x = x, y = y + wibox_height, width = width, height = height }
local dropdown = setmetatable(conf, { __index = quake })
local console = setmetatable(conf, { __index = quake })
capi.client.connect_signal("manage", function(c)
if c.instance == console.name and c.screen == console.screen then
console:display()
end
end)
capi.client.connect_signal("unmanage", function(c)
if c.instance == console.name and c.screen == console.screen then
console.visible = false
end
capi.client.connect_signal("manage", function(c)
if c.instance == dropdown.name and c.screen == dropdown.screen then
dropdown:display()
end
end)
capi.client.connect_signal("unmanage", function(c)
if c.instance == dropdown.name and c.screen == dropdown.screen then
dropdown.visible = false
end
end)
-- "Reattach" currently running quake application. This is in case awesome is restarted.
local reattach = capi.timer { timeout = 0 }
reattach:connect_signal("timeout", function()
reattach:stop()
console:display()
end)
reattach:start()
return console
return dropdown
end
function quake:toggle()
current_tag = awful.tag.selected(self.screen)
if self.last_tag ~= tostring(current_tag) and self.visible then
awful.client.movetotag(current_tag, self:display())
else
self.visible = not self.visible
self:display()
end
if self.followtag then self.screen = awful.screen.focused() end
local current_tag = self.screen.selected_tag
if current_tag and self.last_tag ~= current_tag and self.visible then
self:display():move_to_tag(current_tag)
else
self.visible = not self.visible
self:display()
end
end
setmetatable(quake, { __call = function(_, ...) return quake:new(...) end })
return quake
return setmetatable(quake, { __call = function(_, ...) return quake:new(...) end })

View File

@ -8,15 +8,11 @@
--]]
local wibox = require("wibox")
local beautiful = require("beautiful")
local gears = require("gears")
-- Lain Cairo separators util submodule
-- lain.util.separators
local separators = {}
local height = beautiful.awful_widget_height or 0
local width = beautiful.separators_width or 9
local separators = { height = 0, width = 9 }
-- [[ Arrow
@ -24,7 +20,9 @@ local width = beautiful.separators_width or 9
function separators.arrow_right(col1, col2)
local widget = wibox.widget.base.make_widget()
widget.fit = function(m, w, h) return width, height end
widget.fit = function(m, w, h)
return separators.width, separators.height
end
widget.draw = function(mycross, wibox, cr, width, height)
if col2 ~= "alpha" then
@ -62,7 +60,9 @@ end
function separators.arrow_left(col1, col2)
local widget = wibox.widget.base.make_widget()
widget.fit = function(m, w, h) return width, height end
widget.fit = function(m, w, h)
return separators.width, separators.height
end
widget.draw = function(mycross, wibox, cr, width, height)
if col1 ~= "alpha" then

View File

@ -6,27 +6,26 @@
--]]
local newtimer = require("lain.helpers").newtimer
local async = require("lain.asyncshell")
local wibox = require("wibox")
local helpers = require("lain.helpers")
local textbox = require("wibox.widget.textbox")
local setmetatable = setmetatable
-- Basic template for custom widgets
-- Asynchronous version
-- Template for custom asynchronous widgets
-- lain.widgets.abase
local function worker(args)
local abase = {}
local args = args or {}
local timeout = args.timeout or 5
local cmd = args.cmd or ""
local settings = args.settings or function() end
local abase = {}
local args = args or {}
local timeout = args.timeout or 5
local nostart = args.nostart or false
local stoppable = args.stoppable or false
local cmd = args.cmd
local settings = args.settings or function() widget:set_text(output) end
abase.widget = wibox.widget.textbox('')
abase.widget = args.widget or textbox()
function abase.update()
async.request(cmd, function(f)
helpers.async(cmd, function(f)
output = f
if output ~= abase.prev then
widget = abase.widget
@ -36,9 +35,9 @@ local function worker(args)
end)
end
newtimer(cmd, timeout, abase.update)
abase.timer = helpers.newtimer(cmd, timeout, abase.update, nostart, stoppable)
return setmetatable(abase, { __index = abase.widget })
return abase
end
return setmetatable({}, { __call = function(_, ...) return worker(...) end })

View File

@ -7,21 +7,18 @@
--]]
local newtimer = require("lain.helpers").newtimer
local read_pipe = require("lain.helpers").read_pipe
local helpers = require("lain.helpers")
local shell = require("awful.util").shell
local wibox = require("wibox")
local string = { match = string.match,
format = string.format }
local setmetatable = setmetatable
-- ALSA volume
-- lain.widgets.alsa
local alsa = { last_level = "0", last_status = "" }
local function worker(args)
local alsa = { widget = wibox.widget.textbox() }
local args = args or {}
local timeout = args.timeout or 5
local settings = args.settings or function() end
@ -29,31 +26,31 @@ local function worker(args)
alsa.cmd = args.cmd or "amixer"
alsa.channel = args.channel or "Master"
alsa.togglechannel = args.togglechannel
alsa.widget = wibox.widget.textbox('')
function alsa.update()
mixer = read_pipe(string.format("%s get %s", alsa.cmd, alsa.channel))
l,s = string.match(mixer, "([%d]+)%%.*%[([%l]*)")
local format_cmd = string.format("%s get %s", alsa.cmd, alsa.channel)
-- HDMIs can have a channel different from Master for toggling mute
if alsa.togglechannel then
s = string.match(read_pipe(string.format("%s get %s", alsa.cmd, alsa.togglechannel)), "%[(%a+)%]")
end
if alsa.last_level ~= l or alsa.last_status ~= s then
volume_now = { level = l, status = s }
alsa.last_level = l
alsa.last_status = s
widget = alsa.widget
settings()
end
if alsa.togglechannel then
format_cmd = { shell, "-c", string.format("%s get %s; %s get %s",
alsa.cmd, alsa.channel, alsa.cmd, alsa.togglechannel) }
end
timer_id = string.format("alsa-%s-%s", alsa.cmd, alsa.channel)
newtimer(timer_id, timeout, alsa.update)
alsa.last = {}
return setmetatable(alsa, { __index = alsa.widget })
function alsa.update()
helpers.async(format_cmd, function(mixer)
local l,s = string.match(mixer, "([%d]+)%%.*%[([%l]*)")
if alsa.last.level ~= l or alsa.last.status ~= s then
volume_now = { level = l, status = s }
widget = alsa.widget
settings()
alsa.last = volume_now
end
end)
end
helpers.newtimer(string.format("alsa-%s-%s", alsa.cmd, alsa.channel), timeout, alsa.update)
return alsa
end
return setmetatable(alsa, { __call = function(_, ...) return worker(...) end })
return setmetatable({}, { __call = function(_, ...) return worker(...) end })

View File

@ -7,179 +7,130 @@
--]]
local newtimer = require("lain.helpers").newtimer
local read_pipe = require("lain.helpers").read_pipe
local helpers = require("lain.helpers")
local awful = require("awful")
local beautiful = require("beautiful")
local naughty = require("naughty")
local wibox = require("wibox")
local math = { modf = math.modf }
local mouse = mouse
local string = { format = string.format,
match = string.match,
rep = string.rep }
local type = type
local tonumber = tonumber
local setmetatable = setmetatable
-- ALSA volume bar
-- lain.widgets.alsabar
local alsabar = {
channel = "Master",
step = "1%",
colors = {
background = beautiful.bg_normal,
background = "#000000",
mute = "#EB8F8F",
unmute = "#A4CE8A"
},
terminal = terminal or "xterm",
mixer = terminal .. " -e alsamixer",
notifications = {
font = beautiful.font:sub(beautiful.font:find(""), beautiful.font:find(" ")),
font_size = "11",
color = beautiful.fg_normal,
bar_size = 18,
screen = 1
},
_current_level = 0,
_muted = false
}
function alsabar.notify()
alsabar.update()
local preset = {
title = "",
text = "",
timeout = 5,
screen = alsabar.notifications.screen,
font = alsabar.notifications.font .. " " ..
alsabar.notifications.font_size,
fg = alsabar.notifications.color
}
if alsabar._muted
then
preset.title = alsabar.channel .. " - Muted"
else
preset.title = alsabar.channel .. " - " .. alsabar._current_level .. "%"
end
int = math.modf((alsabar._current_level / 100) * alsabar.notifications.bar_size)
preset.text = "["
.. string.rep("|", int)
.. string.rep(" ", alsabar.notifications.bar_size - int)
.. "]"
if alsabar.followmouse then
preset.screen = mouse.screen
end
if alsabar._notify ~= nil then
alsabar._notify = naughty.notify ({
replaces_id = alsabar._notify.id,
preset = preset,
})
else
alsabar._notify = naughty.notify ({
preset = preset,
})
end
end
local function worker(args)
local args = args or {}
local timeout = args.timeout or 5
local settings = args.settings or function() end
local width = args.width or 63
local height = args.heigth or 1
local height = args.height or 1
local ticks = args.ticks or false
local ticks_size = args.ticks_size or 7
local vertical = args.vertical or false
alsabar.cmd = args.cmd or "amixer"
alsabar.channel = args.channel or alsabar.channel
alsabar.togglechannel = args.togglechannel
alsabar.step = args.step or alsabar.step
alsabar.colors = args.colors or alsabar.colors
alsabar.notifications = args.notifications or alsabar.notifications
alsabar.followmouse = args.followmouse or false
alsabar.cmd = args.cmd or "amixer"
alsabar.channel = args.channel or "Master"
alsabar.togglechannel = args.togglechannel
alsabar.colors = args.colors or alsabar.colors
alsabar.followtag = args.followtag or false
alsabar.notification_preset = args.notification_preset
alsabar.bar = awful.widget.progressbar()
alsabar.bar:set_background_color(alsabar.colors.background)
alsabar.bar:set_color(alsabar.colors.unmute)
alsabar.tooltip = awful.tooltip({ objects = { alsabar.bar } })
alsabar.bar:set_width(width)
alsabar.bar:set_height(height)
alsabar.bar:set_ticks(ticks)
alsabar.bar:set_ticks_size(ticks_size)
alsabar.bar:set_vertical(vertical)
function alsabar.update()
-- Get mixer control contents
local mixer = read_pipe(string.format("%s get %s", alsabar.cmd, alsabar.channel))
-- Capture mixer control state: [5%] ... ... [on]
local volu, mute = string.match(mixer, "([%d]+)%%.*%[([%l]*)")
-- HDMIs can have a channel different from Master for toggling mute
if alsabar.togglechannel then
mute = string.match(read_pipe(string.format("%s get %s", alsabar.cmd, alsabar.togglechannel)), "%[(%a+)%]")
end
if (volu and tonumber(volu) ~= alsabar._current_level) or (mute and string.match(mute, "on") ~= alsabar._muted)
then
alsabar._current_level = tonumber(volu) or alsabar._current_level
alsabar.bar:set_value(alsabar._current_level / 100)
if not mute and tonumber(volu) == 0 or mute == "off"
then
alsabar._muted = true
alsabar.tooltip:set_text (" [Muted] ")
alsabar.bar:set_color(alsabar.colors.mute)
else
alsabar._muted = false
alsabar.tooltip:set_text(string.format(" %s:%s ", alsabar.channel, volu))
alsabar.bar:set_color(alsabar.colors.unmute)
end
volume_now = {}
volume_now.level = tonumber(volu)
volume_now.status = mute
settings()
end
if not alsabar.notification_preset then
alsabar.notification_preset = {}
alsabar.notification_preset.font = "Monospace 10"
end
alsabar.bar:buttons(awful.util.table.join (
awful.button({}, 1, function()
awful.util.spawn(alsabar.mixer)
end),
awful.button({}, 2, function()
awful.util.spawn(string.format("%s set %s 100%%", alsabar.cmd, alsabar.channel))
pulsebar.update()
end),
awful.button({}, 3, function()
awful.util.spawn(string.format("%s set %s toggle", alsabar.cmd, alsabar.channel))
alsabar.update()
end),
awful.button({}, 4, function()
awful.util.spawn(string.format("%s set %s %s+", alsabar.cmd, alsabar.channel, alsabar.step))
alsabar.update()
end),
awful.button({}, 5, function()
awful.util.spawn(string.format("%s set %s %s-", alsabar.cmd, alsabar.channel, alsabar.step))
alsabar.update()
end)
))
local format_cmd = string.format("%s get %s", alsabar.cmd, alsabar.channel)
timer_id = string.format("alsabar-%s-%s", alsabar.cmd, alsabar.channel)
if alsabar.togglechannel then
format_cmd = { awful.util.shell, "-c", string.format("%s get %s; %s get %s",
alsabar.cmd, alsabar.channel, alsabar.cmd, alsabar.togglechannel) }
end
newtimer(timer_id, timeout, alsabar.update)
alsabar.bar = wibox.widget {
forced_height = height,
forced_width = width,
color = alsabar.colors.unmute,
background_color = alsabar.colors.background,
margins = 1,
paddings = 1,
ticks = ticks,
ticks_size = ticks_size,
widget = wibox.widget.progressbar,
layout = vertical and wibox.container.rotate
}
alsabar.tooltip = awful.tooltip({ objects = { alsabar.bar } })
function alsabar.update(callback)
helpers.async(format_cmd, function(mixer)
local volu,mute = string.match(mixer, "([%d]+)%%.*%[([%l]*)")
if (volu and tonumber(volu) ~= alsabar._current_level) or (mute and string.match(mute, "on") ~= alsabar._muted) then
alsabar._current_level = tonumber(volu) or alsabar._current_level
alsabar.bar:set_value(alsabar._current_level / 100)
if (not mute and tonumber(volu) == 0) or mute == "off" then
alsabar._muted = true
alsabar.tooltip:set_text ("[Muted]")
alsabar.bar.color = alsabar.colors.mute
else
alsabar._muted = false
alsabar.tooltip:set_text(string.format("%s: %s", alsabar.channel, volu))
alsabar.bar.color = alsabar.colors.unmute
end
volume_now = {}
volume_now.level = tonumber(volu)
volume_now.status = mute
settings()
if type(callback) == "function" then callback() end
end
end)
end
function alsabar.notify()
alsabar.update(function()
local preset = alsabar.notification_preset
if alsabar._muted then
preset.title = string.format("%s - Muted", alsabar.channel)
else
preset.title = string.format("%s - %s%%", alsabar.channel, alsabar._current_level)
end
int = math.modf((alsabar._current_level / 100) * awful.screen.focused().mywibox.height)
preset.text = string.format("[%s%s]", string.rep("|", int),
string.rep(" ", awful.screen.focused().mywibox.height - int))
if alsabar.followtag then preset.screen = awful.screen.focused() end
if not alsabar.notification then
alsabar.notification = naughty.notify {
preset = preset,
destroy = function() alsabar.notification = nil end
}
else
naughty.replace_text(alsabar.notification, preset.title, preset.text)
end
end)
end
helpers.newtimer(string.format("alsabar-%s-%s", alsabar.cmd, alsabar.channel), timeout, alsabar.update)
return alsabar
end

View File

@ -1,42 +0,0 @@
--[[
Licensed under GNU General Public License v2
* (c) 2014, Luke Bonham
--]]
local newtimer = require("lain.helpers").newtimer
local read_pipe = require("lain.helpers").read_pipe
local wibox = require("wibox")
local setmetatable = setmetatable
-- Basic template for custom widgets
-- lain.widgets.base
local function worker(args)
local base = {}
local args = args or {}
local timeout = args.timeout or 5
local cmd = args.cmd or ""
local settings = args.settings or function() end
base.widget = wibox.widget.textbox('')
function base.update()
output = read_pipe(cmd)
if output ~= base.prev then
widget = base.widget
settings()
base.prev = output
end
end
newtimer(cmd, timeout, base.update)
return setmetatable(base, { __index = base.widget })
end
return setmetatable({}, { __call = function(_, ...) return worker(...) end })

View File

@ -7,18 +7,16 @@
--]]
local newtimer = require("lain.helpers").newtimer
local first_line = require("lain.helpers").first_line
local newtimer = require("lain.helpers").newtimer
local naughty = require("naughty")
local wibox = require("wibox")
local math = { abs = math.abs,
floor = math.floor,
log10 = math.log10,
min = math.min }
local string = { format = string.format }
local ipairs = ipairs
local type = type
local tonumber = tonumber
local setmetatable = setmetatable
@ -27,7 +25,7 @@ local setmetatable = setmetatable
-- lain.widgets.bat
local function worker(args)
local bat = {}
local bat = { widget = wibox.widget.textbox() }
local args = args or {}
local timeout = args.timeout or 30
local batteries = args.batteries or (args.battery and {args.battery}) or {"BAT0"}
@ -35,8 +33,6 @@ local function worker(args)
local notify = args.notify or "on"
local settings = args.settings or function() end
bat.widget = wibox.widget.textbox('')
bat_notification_low_preset = {
title = "Battery low",
text = "Plug the cable!",
@ -110,35 +106,48 @@ local function worker(args)
end
end
-- When one of the battery is charging, others' status are either
-- "Full", "Unknown" or "Charging". When the laptop is not plugged in,
-- one or more of the batteries may be full, but only one battery
-- discharging suffices to set global status to "Discharging".
bat_now.status = bat_now.n_status[1]
for _,status in ipairs(bat_now.n_status) do
if status == "Discharging" or status == "Charging" then
bat_now.status = status
end
end
bat_now.ac_status = tonumber(first_line(string.format("%s%s/online", pspath, ac))) or "N/A"
if bat_now.status ~= "N/A" then
if bat_now.status ~= "Full" and sum_rate_power == 0 and bat_now.ac_status == 1 then
bat_now.perc = math.floor(math.min(100, (sum_energy_now / sum_energy_full) * 100))
bat_now.time = "00:00"
bat_now.watt = 0
-- update {perc,time,watt} iff battery not full and rate > 0
if bat_now.status ~= "Full" and (sum_rate_power > 0 or sum_rate_current > 0) then
elseif bat_now.status ~= "Full" then
local rate_time = 0
local div = (sum_rate_power > 0 and sum_rate_power) or sum_rate_current
-- Calculate time and watt if rates are greater then 0
if (sum_rate_power > 0 or sum_rate_current > 0) then
local div = (sum_rate_power > 0 and sum_rate_power) or sum_rate_current
if bat_now.status == "Charging" then
rate_time = (sum_energy_full - sum_energy_now) / div
else -- Discharging
rate_time = sum_energy_now / div
end
if bat_now.status == "Charging" then
rate_time = (sum_energy_full - sum_energy_now) / div
else -- Discharging
rate_time = sum_energy_now / div
end
if 0 < rate_time and rate_time < 0.01 then -- check for magnitude discrepancies (#199)
rate_time_magnitude = math.abs(math.floor(math.log10(rate_time)))
rate_time = rate_time * 10^(rate_time_magnitude - 2)
end
if 0 < rate_time and rate_time < 0.01 then -- check for magnitude discrepancies (#199)
rate_time_magnitude = math.abs(math.floor(math.log10(rate_time)))
rate_time = rate_time * 10^(rate_time_magnitude - 2)
end
end
local hours = math.floor(rate_time)
local minutes = math.floor((rate_time - hours) * 60)
bat_now.perc = math.floor(math.min(100, (sum_energy_now / sum_energy_full) * 100))
bat_now.time = string.format("%02d:%02d", hours, minutes)
bat_now.watt = tonumber(string.format("%.2f", sum_rate_energy / 1e6))
elseif bat_now.status ~= "Full" and sum_rate_power == 0 and bat_now.ac_status == 1 then
bat_now.perc = math.floor(math.min(100, (sum_energy_now / sum_energy_full) * 100))
bat_now.time = "00:00"
bat_now.watt = 0
elseif bat_now.status == "Full" then
bat_now.perc = 100
bat_now.time = "00:00"
@ -165,9 +174,9 @@ local function worker(args)
end
end
newtimer(battery, timeout, bat.update)
newtimer("batteries", timeout, bat.update)
return setmetatable(bat, { __index = bat.widget })
return bat
end
return setmetatable({}, { __call = function(_, ...) return worker(...) end })

View File

@ -1,58 +0,0 @@
--[[
Licensed under GNU General Public License v2
* (c) 2013, Luke Bonham
* (c) 2010-2012, Peter Hofmann
--]]
local wibox = require("awful.wibox")
local setmetatable = setmetatable
-- Creates a thin wibox at a position relative to another wibox
-- lain.widgets.borderbox
local borderbox = {}
local function worker(relbox, s, args)
local where = args.position or 'top'
local color = args.color or '#FFFFFF'
local size = args.size or 1
local box = nil
local wiboxarg = { position = nil, bg = color }
if where == 'top'
then
wiboxarg.width = relbox.width
wiboxarg.height = size
box = wibox(wiboxarg)
box.x = relbox.x
box.y = relbox.y - size
elseif where == 'bottom'
then
wiboxarg.width = relbox.width
wiboxarg.height = size
box = wibox(wiboxarg)
box.x = relbox.x
box.y = relbox.y + relbox.height
elseif where == 'left'
then
wiboxarg.width = size
wiboxarg.height = relbox.height
box = wibox(wiboxarg)
box.x = relbox.x - size
box.y = relbox.y
elseif where == 'right'
then
wiboxarg.width = size
wiboxarg.height = relbox.height
box = wibox(wiboxarg)
box.x = relbox.x + relbox.width
box.y = relbox.y
end
box.screen = s
return box
end
return setmetatable(borderbox, { __call = function(_, ...) return worker(...) end })

View File

@ -6,54 +6,43 @@
--]]
local icons_dir = require("lain.helpers").icons_dir
local helpers = require("lain.helpers")
local markup = require("lain.util.markup")
local awful = require("awful")
local beautiful = require("beautiful")
local naughty = require("naughty")
local io = { popen = io.popen }
local os = { date = os.date }
local mouse = mouse
local os = { date = os.date }
local string = { format = string.format,
sub = string.sub,
gsub = string.gsub }
local ipairs = ipairs
local tonumber = tonumber
local setmetatable = setmetatable
-- Calendar notification
-- lain.widgets.calendar
local calendar = {}
local cal_notification = nil
local calendar = { offset = 0 }
function calendar.hide()
if cal_notification ~= nil then
naughty.destroy(cal_notification)
cal_notification = nil
end
if not calendar.notification then return end
naughty.destroy(calendar.notification)
calendar.notification = nil
end
function calendar.show(t_out, inc_offset, scr)
calendar.hide()
local f, c_text
local offs = inc_offset or 0
local tims = t_out or 0
local today = tonumber(os.date('%d'))
local today = os.date("%e")
local offs = inc_offset or 0
local f
calendar.offset = calendar.offset + offs
if offs == 0 or calendar.offset == 0
then -- current month showing, today highlighted
calendar.offset = 0
calendar.notify_icon = calendar.icons .. today .. ".png"
local current_month = (offs == 0 or calendar.offset == 0)
-- bg and fg inverted to highlight today
f = io.popen(calendar.cal_format(today))
if current_month then -- today highlighted
calendar.offset = 0
calendar.notify_icon = string.format("%s%s.png", calendar.icons, tonumber(today))
f = calendar.cal
else -- no current month showing, no day to highlight
local month = tonumber(os.date('%m'))
local year = tonumber(os.date('%Y'))
local month = tonumber(os.date("%m"))
local year = tonumber(os.date("%Y"))
month = month + calendar.offset
@ -68,55 +57,30 @@ function calendar.show(t_out, inc_offset, scr)
end
calendar.notify_icon = nil
f = io.popen(string.format('%s %s %s', calendar.cal, month, year))
f = string.format("%s %s %s", calendar.cal, month, year)
end
c_text = "<tt><span font='" .. calendar.font .. " "
.. calendar.font_size .. "'><b>"
.. f:read() .. "</b>\n\n"
.. f:read() .. "\n"
.. f:read("*all"):gsub("\n*$", "")
.. "</span></tt>"
f:close()
if calendar.followmouse then
scrp = mouse.screen
if calendar.followtag then
calendar.notification_preset.screen = awful.screen.focused()
else
scrp = scr or calendar.scr_pos
calendar.notification_preset.screen = src or 1
end
cal_notification = naughty.notify({
text = c_text,
icon = calendar.notify_icon,
position = calendar.position,
fg = calendar.fg,
bg = calendar.bg,
timeout = tims,
screen = scrp
})
helpers.async(f, function(ws)
fg, bg = calendar.notification_preset.fg, calendar.notification_preset.bg
ws = ws:gsub("%c%[%d+[m]?%s?%d+%c%[%d+[m]?", markup.bold(markup.color(bg, fg, today)))
calendar.hide()
calendar.notification = naughty.notify({
preset = calendar.notification_preset,
text = ws:gsub("\n*$", ""),
icon = calendar.notify_icon,
timeout = t_out or calendar.notification_preset.timeout or 5
})
end)
end
function calendar.attach(widget, args)
local args = args or {}
calendar.cal = args.cal or "/usr/bin/cal"
calendar.cal_format = args.cal_format or function(today)
return string.format("%s | sed -r -e 's/_\\x08//g' -e '0,/(^| )%d($| )/ s/(^| )%d($| )/\\1<b><span foreground=\"%s\" background=\"%s\">%d<\\/span><\\/b>\\2/'",
calendar.cal, today, today, calendar.bg, calendar.fg, today)
end
calendar.icons = args.icons or icons_dir .. "cal/white/"
calendar.font = args.font or beautiful.font:gsub(" %d.*", "")
calendar.font_size = tonumber(args.font_size) or 11
calendar.fg = args.fg or beautiful.fg_normal or "#FFFFFF"
calendar.bg = args.bg or beautiful.bg_normal or "#000000"
calendar.position = args.position or "top_right"
calendar.scr_pos = args.scr_pos or 1
calendar.followmouse = args.followmouse or false
calendar.offset = 0
calendar.notify_icon = nil
widget:connect_signal("mouse::enter", function () calendar.show(0, 0, calendar.scr_pos) end)
function calendar.attach(widget)
widget:connect_signal("mouse::enter", function () calendar.show(0) end)
widget:connect_signal("mouse::leave", function () calendar.hide() end)
widget:buttons(awful.util.table.join(awful.button({ }, 1, function ()
calendar.show(0, -1, calendar.scr_pos) end),
@ -128,4 +92,23 @@ function calendar.attach(widget, args)
calendar.show(0, 1, calendar.scr_pos) end)))
end
return setmetatable(calendar, { __call = function(_, ...) return create(...) end })
local function worker(args)
local args = args or {}
calendar.cal = args.cal or "/usr/bin/cal"
calendar.attach_to = args.attach_to or {}
calendar.followtag = args.followtag or false
calendar.icons = args.icons or helpers.icons_dir .. "cal/white/"
calendar.notification_preset = args.notification_preset
if not calendar.notification_preset then
calendar.notification_preset = {
font = "Monospace 10",
fg = "#FFFFFF",
bg = "#000000"
}
end
for i, widget in ipairs(calendar.attach_to) do calendar.attach(widget) end
end
return setmetatable(calendar, { __call = function(_, ...) return worker(...) end })

View File

@ -6,14 +6,15 @@
--]]
local helpers = require("lain.helpers")
local json = require("lain.util.dkjson")
local pread = require("awful.util").pread
local naughty = require("naughty")
local wibox = require("wibox")
local mouse = mouse
local os = { getenv = os.getenv }
local helpers = require("lain.helpers")
local json = require("lain.util.dkjson")
local focused = require("awful.screen").focused
local pread = require("awful.util").pread
local naughty = require("naughty")
local wibox = require("wibox")
local next = next
local os = { getenv = os.getenv }
local table = table
local setmetatable = setmetatable
-- Google Play Music Desktop infos
@ -24,12 +25,12 @@ local function worker(args)
local args = args or {}
local timeout = args.timeout or 2
local notify = args.notify or "off"
local followmouse = args.followmouse or false
local followtag = args.followtag or false
local file_location = args.file_location or
os.getenv("HOME") .. "/.config/Google Play Music Desktop Player/json_store/playback.json"
local settings = args.settings or function() end
gpmdp.widget = wibox.widget.textbox('')
gpmdp.widget = wibox.widget.textbox()
gpmdp_notification_preset = {
title = "Now playing",
@ -39,14 +40,13 @@ local function worker(args)
helpers.set_map("gpmdp_current", nil)
function gpmdp.update()
file, err = io.open(file_location, "r")
if not file
then
gpm_now = { running = false, playing = false }
local filelines = helpers.lines_from(file_location)
if not next(filelines) then
local gpm_now = { running = false, playing = false }
else
dict, pos, err = json.decode(file:read "*a", 1, nil)
file:close()
gpm_now = {}
dict, pos, err = json.decode(table.concat(filelines), 1, nil)
local gpm_now = {}
gpm_now.artist = dict.song.artist
gpm_now.album = dict.song.album
gpm_now.title = dict.song.title
@ -54,7 +54,7 @@ local function worker(args)
gpm_now.playing = dict.playing
end
if (pread("pidof 'Google Play Music Desktop Player'") ~= '') then
if pread("pidof 'Google Play Music Desktop Player'") ~= '' then
gpm_now.running = true
else
gpm_now.running = false
@ -64,32 +64,29 @@ local function worker(args)
widget = gpmdp.widget
settings()
if gpm_now.playing
then
if notify == "on" and gpm_now.title ~= helpers.get_map("gpmdp_current")
then
if gpm_now.playing then
if notify == "on" and gpm_now.title ~= helpers.get_map("gpmdp_current") then
helpers.set_map("gpmdp_current", gpm_now.title)
os.execute("curl " .. gpm_now.cover_url .. " -o /tmp/gpmcover.png")
if followmouse then
gpmdp_notification_preset.screen = mouse.screen
end
if followtag then gpmdp_notification_preset.screen = focused() end
gpmdp.id = naughty.notify({
preset = gpmdp_notification_preset,
icon = "/tmp/gpmcover.png",
replaces_id = gpmdp.id,
}).id
helpers.async(string.format("curl %d -o /tmp/gpmcover.png", gpm_now.cover_url),
function(f)
gpmdp.id = naughty.notify({
preset = gpmdp_notification_preset,
icon = "/tmp/gpmcover.png",
replaces_id = gpmdp.id
}).id
end)
end
elseif not gpm_now.running
then
elseif not gpm_now.running then
helpers.set_map("gpmdp_current", nil)
end
end
helpers.newtimer("gpmdp", timeout, gpmdp.update)
gpmdp.timer = helpers.newtimer("gpmdp", timeout, gpmdp.update, true, true)
return setmetatable(gpmdp, { __index = gpmdp.widget })
return gpmdp
end
return setmetatable(gpmdp, { __call = function(_, ...) return worker(...) end })

View File

@ -6,24 +6,21 @@
--]]
local newtimer = require("lain.helpers").newtimer
local read_pipe = require("lain.helpers").read_pipe
local wibox = require("wibox")
local helpers = require("lain.helpers")
local awful = require("awful")
local string = { match = string.match }
local wibox = require("wibox")
local string = { format = string.format,
match = string.match }
local execute = os.execute
local setmetatable = setmetatable
-- Keyboard layout switcher
-- lain.widgets.contrib.kblayout
local kbdlayout = {}
local function worker(args)
local kbdlayout = {}
kbdlayout.widget = wibox.widget.textbox('')
local layouts = args.layouts
local args = args or {}
local layouts = args.layouts or {}
local settings = args.settings or function () end
local add_us_secondary = true
local timeout = args.timeout or 5
@ -31,52 +28,53 @@ local function worker(args)
if args.add_us_secondary == false then add_us_secondary = false end
-- Mouse bindings
kbdlayout.widget:buttons(awful.util.table.join(
awful.button({ }, 1, function () kbdlayout.next() end),
awful.button({ }, 3, function () kbdlayout.prev() end)))
kbdlayout.widget = wibox.widget.textbox()
local function run_settings(layout, variant)
local function kbd_run_settings(layout, variant)
kbdlayout_now = {
layout = string.match(layout, "[^,]+"), -- Make sure to match the primary layout only.
variant = variant
}
widget = kbdlayout.widget
kbdlayout_now = { layout=string.match(layout, "[^,]+"), -- Make sure to match the primary layout only.
variant=variant }
settings()
end
function kbdlayout.update()
local status = read_pipe('setxkbmap -query')
run_settings(string.match(status, "layout:%s*([^\n]*)"),
string.match(status, "variant:%s*([^\n]*)"))
helpers.async("setxkbmap -query", function(status)
kbd_run_settings(string.match(status, "layout:%s*([^\n]*)"),
string.match(status, "variant:%s*([^\n]*)"))
end)
end
function kbdlayout.set(i)
if #layouts == 0 then return end
idx = ((i - 1) % #layouts) + 1 -- Make sure to wrap around as needed.
local to_execute = 'setxkbmap ' .. layouts[idx].layout
local to_execute = "setxkbmap " .. layouts[idx].layout
if add_us_secondary and not string.match(layouts[idx].layout, ",?us,?") then
to_execute = to_execute .. ",us"
end
if layouts[idx].variant then
to_execute = to_execute .. ' ' .. layouts[idx].variant
to_execute = to_execute .. " " .. layouts[idx].variant
end
if os.execute(to_execute) then
run_settings(layouts[idx].layout, layouts[idx].variant)
if execute(to_execute) then
kbd_run_settings(layouts[idx].layout, layouts[idx].variant)
end
end
function kbdlayout.next()
kbdlayout.set(idx + 1)
end
function kbdlayout.next() kbdlayout.set(idx + 1) end
function kbdlayout.prev() kbdlayout.set(idx - 1) end
function kbdlayout.prev()
kbdlayout.set(idx - 1)
end
-- Mouse bindings
kbdlayout.widget:buttons(awful.util.table.join(
awful.button({ }, 1, function () kbdlayout.next() end),
awful.button({ }, 3, function () kbdlayout.prev() end)))
newtimer("kbdlayout", timeout, kbdlayout.update)
return setmetatable(kbdlayout, { __index = kbdlayout.widget })
helpers.newtimer("kbdlayout", timeout, kbdlayout.update)
return kbdlayout
end
return setmetatable({}, { __call = function (_, ...) return worker(...) end })

View File

@ -6,19 +6,15 @@
--]]
local helpers = require("lain.helpers")
local async = require("lain.asyncshell")
local escape_f = require("awful.util").escape
local naughty = require("naughty")
local wibox = require("wibox")
local io = { popen = io.popen }
local os = { execute = os.execute,
getenv = os.getenv }
local string = { format = string.format,
gmatch = string.gmatch }
local helpers = require("lain.helpers")
local shell = require("awful.util").shell
local focused = require("awful.screen").focused
local escape_f = require("awful.util").escape
local naughty = require("naughty")
local wibox = require("wibox")
local os = { getenv = os.getenv }
local string = { format = string.format,
gmatch = string.gmatch }
local setmetatable = setmetatable
-- MOC audio player
@ -26,31 +22,23 @@ local setmetatable = setmetatable
local moc = {}
local function worker(args)
local args = args or {}
local timeout = args.timeout or 2
local music_dir = args.music_dir or os.getenv("HOME") .. "/Music"
local cover_size = args.cover_size or 100
local default_art = args.default_art or ""
local followmouse = args.followmouse or false
local settings = args.settings or function() end
local args = args or {}
local timeout = args.timeout or 2
local music_dir = args.music_dir or os.getenv("HOME") .. "/Music"
local cover_pattern = args.cover_pattern or "*\\.(jpg|jpeg|png|gif)$"
local cover_size = args.cover_size or 100
local default_art = args.default_art or ""
local followtag = args.followtag or false
local settings = args.settings or function() end
local mpdcover = helpers.scripts_dir .. "mpdcover"
moc.widget = wibox.widget.textbox()
moc.widget = wibox.widget.textbox('')
moc_notification_preset = {
title = "Now playing",
timeout = 6
}
moc_notification_preset = { title = "Now playing", timeout = 6 }
helpers.set_map("current moc track", nil)
function moc.update()
-- mocp -i will produce output like:
-- Artist: Travis
-- Album: The Man Who
-- etc.
async.request("mocp -i", function(f)
helpers.async("mocp -i", function(f)
moc_now = {
state = "N/A",
file = "N/A",
@ -63,13 +51,13 @@ local function worker(args)
for line in string.gmatch(f, "[^\n]+") do
for k, v in string.gmatch(line, "([%w]+):[%s](.*)$") do
if k == "State" then moc_now.state = v
elseif k == "File" then moc_now.file = v
elseif k == "Artist" then moc_now.artist = escape_f(v)
elseif k == "SongTitle" then moc_now.title = escape_f(v)
elseif k == "Album" then moc_now.album = escape_f(v)
if k == "State" then moc_now.state = v
elseif k == "File" then moc_now.file = v
elseif k == "Artist" then moc_now.artist = escape_f(v)
elseif k == "SongTitle" then moc_now.title = escape_f(v)
elseif k == "Album" then moc_now.album = escape_f(v)
elseif k == "CurrentTime" then moc_now.elapsed = escape_f(v)
elseif k == "TotalTime" then moc_now.total = escape_f(v)
elseif k == "TotalTime" then moc_now.total = escape_f(v)
end
end
end
@ -82,18 +70,22 @@ local function worker(args)
if moc_now.state == "PLAY" then
if moc_now.title ~= helpers.get_map("current moc track") then
helpers.set_map("current moc track", moc_now.title)
os.execute(string.format("%s %q %q %d %q", mpdcover, "",
moc_now.file, cover_size, default_art))
if followmouse then
moc_notification_preset.screen = mouse.screen
end
if followtag then moc_notification_preset.screen = focused() end
moc.id = naughty.notify({
preset = moc_notification_preset,
icon = "/tmp/mpdcover.png",
local common = {
preset = moc_notification_preset,
icon = default_art,
icon_size = cover_size,
replaces_id = moc.id,
}).id
}
local path = string.format("%s/%s", music_dir, string.match(moc_now.file, ".*/"))
local cover = string.format("find '%s' -maxdepth 1 -type f | egrep -i -m1 '%s'", path, cover_pattern)
helpers.async({ shell, "-c", cover }, function(current_icon)
common.icon = current_icon:gsub("\n", "")
moc.id = naughty.notify(common).id
end)
end
elseif moc_now.state ~= "PAUSE" then
helpers.set_map("current moc track", nil)
@ -101,9 +93,9 @@ local function worker(args)
end)
end
helpers.newtimer("moc", timeout, moc.update)
moc.timer = helpers.newtimer("moc", timeout, moc.update, true, true)
return setmetatable(moc, { __index = moc.widget })
return moc
end
return setmetatable(moc, { __call = function(_, ...) return worker(...) end })

View File

@ -6,73 +6,49 @@
--]]
local awful = require("awful")
local os = os
local spawn = awful.util.spawn_with_shell
local setmetatable = setmetatable
local async = require("lain.helpers").async
local awful = require("awful")
local execute = os.execute
local type = type
-- Redshift
-- lain.widgets.contrib.redshift
local redshift = {}
local redshift = { active = false, pid = nil }
local attached = false -- true if attached to a widget
local active = false -- true if redshift is active
local running = false -- true if redshift was initialized
local update_fnct = function() end -- Function that is run each time redshift is toggled. See redshift:attach().
local function init()
-- As there is no way to determine if redshift was previously
-- toggled off (i.e Awesome on-the-fly restart), kill redshift to make sure
os.execute("pkill redshift")
-- Remove existing color adjustment
spawn("redshift -x")
-- (Re)start redshift
spawn("redshift")
running = true
active = true
function redshift:start()
execute("pkill redshift")
awful.spawn.with_shell("redshift -x") -- clear adjustments
redshift.pid = awful.spawn.with_shell("redshift")
redshift.active = true
if type(redshift.update_fun) == "function" then
redshift.update_fun(redshift.active)
end
end
function redshift:toggle()
if running then
-- Sending -USR1 toggles redshift (See project website)
os.execute("pkill -USR1 redshift")
active = not active
else
init()
end
update_fnct()
end
function redshift:off()
if running and active then
redshift:toggle()
end
end
function redshift:on()
if not active then
redshift:toggle()
end
end
function redshift:is_active()
return active
async({ awful.util.shell, "-c", string.format("ps -p %d -o pid=", redshift.pid) }, function(f)
if f and #f > 0 then -- redshift is running
-- Sending -USR1 toggles redshift (See project website)
execute("pkill -USR1 redshift")
redshift.active = not redshift.active
else -- not started or killed, (re)start it
redshift:start()
end
redshift.update_fun(redshift.active)
end)
end
-- Attach to a widget
-- Provides a button which toggles redshift on/off on click
-- @param widget: Widget to attach to.
-- @param fnct: Function to be run each time redshift is toggled (optional).
-- @param fun: Function to be run each time redshift is toggled (optional).
-- Use it to update widget text or icons on status change.
function redshift:attach(widget, fnct)
update_fnct = fnct or function() end
if not attached then
init()
attached = true
update_fnct()
function redshift:attach(widget, fun)
redshift.update_fun = fun or function() end
if not redshift.pid then redshift:start() end
if widget then
widget:buttons(awful.util.table.join(awful.button({}, 1, function () redshift:toggle() end)))
end
widget:buttons(awful.util.table.join( awful.button({}, 1, function () redshift:toggle() end) ))
end
return setmetatable(redshift, { _call = function(_, ...) return create(...) end })
return redshift

View File

@ -6,145 +6,77 @@
--]]
local icons_dir = require("lain.helpers").icons_dir
local awful = require("awful")
local beautiful = require("beautiful")
local naughty = require("naughty")
local mouse = mouse
local io = io
local string = { len = string.len }
local tonumber = tonumber
local setmetatable = setmetatable
local helpers = require("lain.helpers")
local markup = require("lain.util").markup
local awful = require("awful")
local naughty = require("naughty")
local string = { format = string.format, gsub = string.gsub }
-- Taskwarrior notification
-- lain.widgets.contrib.task
local task = {}
local task_notification = nil
function findLast(haystack, needle)
local i=haystack:match(".*"..needle.."()")
if i==nil then return nil else return i-1 end
end
function task.hide()
if task_notification ~= nil then
naughty.destroy(task_notification)
task_notification = nil
end
if not task.notification then return end
naughty.destroy(task.notification)
task.notification = nil
end
function task.show(scr_pos)
function task.show(scr)
task.hide()
local f, c_text, scrp
if task.followmouse then
scrp = mouse.screen
else
scrp = scr_pos or task.scr_pos
if task.followtag then
task.notification_preset.screen = awful.screen.focused()
elseif scr then
task.notification_preset.screen = scr
end
f = io.popen('task ' .. task.cmdline)
c_text = "<span font='"
.. task.font .. " "
.. task.font_size .. "'>"
.. awful.util.escape(f:read("*all"):gsub("\n*$", ""))
.. "</span>"
f:close()
task_notification = naughty.notify({ title = "[task next]",
text = c_text,
icon = task.notify_icon,
position = task.position,
fg = task.fg,
bg = task.bg,
timeout = task.timeout,
screen = scrp
})
helpers.async(task.show_cmd, function(f)
task.notification = naughty.notify({
preset = task.notification_preset,
title = task.show_cmd,
text = markup.font(task.notification_preset.font,
awful.util.escape(f:gsub("\n*$", "")))
})
end)
end
function task.prompt_add()
awful.prompt.run({ prompt = "Add task: " },
mypromptbox[mouse.screen].widget,
function (...)
local f = io.popen("task add " .. ...)
c_text = "\n<span font='"
.. task.font .. " "
.. task.font_size .. "'>"
.. awful.util.escape(f:read("*all"))
.. "</span>"
f:close()
naughty.notify({
text = c_text,
icon = task.notify_icon,
position = task.position,
fg = task.fg,
bg = task.bg,
timeout = task.timeout,
})
end,
nil,
awful.util.getdir("cache") .. "/history_task_add")
end
function task.prompt_search()
awful.prompt.run({ prompt = "Search task: " },
mypromptbox[mouse.screen].widget,
function (...)
local f = io.popen("task " .. ...)
c_text = f:read("*all"):gsub(" \n*$", "")
f:close()
if string.len(c_text) == 0
then
c_text = "No results found."
else
c_text = "<span font='"
.. task.font .. " "
.. task.font_size .. "'>"
.. awful.util.escape(c_text)
.. "</span>"
end
naughty.notify({
title = "[task next " .. ... .. "]",
text = c_text,
icon = task.notify_icon,
position = task.position,
fg = task.fg,
bg = task.bg,
timeout = task.timeout,
screen = mouse.screen
})
end,
nil,
awful.util.getdir("cache") .. "/history_task")
function task.prompt()
awful.prompt.run {
prompt = task.prompt_text,
textbox = awful.screen.focused().mypromptbox.widget,
exe_callback = function(t)
helpers.async(t, function(f)
naughty.notify {
preset = task.notification_preset,
title = t,
text = markup.font(task.notification_preset.font,
awful.util.escape(f:gsub("\n*$", "")))
}
end)
end,
history_path = awful.util.getdir("cache") .. "/history_task"
}
end
function task.attach(widget, args)
local args = args or {}
local args = args or {}
task.show_cmd = args.show_cmd or "task next"
task.prompt_text = args.prompt_text or "Enter task command: "
task.followtag = args.followtag or false
task.notification_preset = args.notification_preset
task.font_size = tonumber(args.font_size) or 12
task.font = args.font or beautiful.font:sub(beautiful.font:find(""),
findLast(beautiful.font, " "))
task.fg = args.fg or beautiful.fg_normal or "#FFFFFF"
task.bg = args.bg or beautiful.bg_normal or "#FFFFFF"
task.position = args.position or "top_right"
task.timeout = args.timeout or 7
task.scr_pos = args.scr_pos or 1
task.followmouse = args.followmouse or false
task.cmdline = args.cmdline or "next"
if not task.notification_preset then
task.notification_preset = {
font = "Monospace 10",
icon = helpers.icons_dir .. "/taskwarrior.png"
}
end
task.notify_icon = icons_dir .. "/taskwarrior/task.png"
task.notify_icon_small = icons_dir .. "/taskwarrior/tasksmall.png"
widget:connect_signal("mouse::enter", function () task.show(task.scr_pos) end)
widget:connect_signal("mouse::leave", function () task.hide() end)
if widget then
widget:connect_signal("mouse::enter", function () task.show() end)
widget:connect_signal("mouse::leave", function () task.hide() end)
end
end
return setmetatable(task, { __call = function(_, ...) return create(...) end })
return task

View File

@ -17,39 +17,33 @@
local debug = { getinfo = debug.getinfo }
local newtimer = require("lain.helpers").newtimer
local first_line = require("lain.helpers").first_line
local beautiful = require("beautiful")
local naughty = require("naughty")
local wibox = require("wibox")
local string = { format = string.format }
local math = { floor = math.floor }
local tostring = tostring
local setmetatable = setmetatable
package.path = debug.getinfo(1,"S").source:match[[^@?(.*[\/])[^\/]-$]] .. "?.lua;" .. package.path
local smapi = require("smapi")
-- ThinkPad SMAPI-enabled battery info widget
-- lain.widgets.contrib.tpbat
local tpbat = { }
local tpbat_notification = nil
local tpbat = {}
function tpbat:hide()
if tpbat_notification ~= nil
then
naughty.destroy(tpbat_notification)
tpbat_notification = nil
end
function tpbat.hide()
if not tpbat.notification then return end
naughty.destroy(tpbat.notification)
tpbat.notification = nil
end
function tpbat:show(t_out)
tpbat:hide()
function tpbat.show(t_out)
tpbat.hide()
local bat = self.bat
local t_out = t_out or 0
local bat = tpbat.bat
if bat == nil or not bat:installed() then return end
local t_out = t_out or 0
local mfgr = bat:get('manufacturer') or "no_mfgr"
local model = bat:get('model') or "no_model"
local chem = bat:get('chemistry') or "no_chem"
@ -57,10 +51,8 @@ function tpbat:show(t_out)
local time = bat:remaining_time()
local msg = "\t"
if status ~= "idle" and status ~= "nil"
then
if time == "N/A"
then
if status ~= "idle" and status ~= "nil" then
if time == "N/A" then
msg = "...Calculating time remaining..."
else
msg = time .. (status == "charging" and " until charged" or " remaining")
@ -72,11 +64,10 @@ function tpbat:show(t_out)
local str = string.format("%s : %s %s (%s)\n", bat.name, mfgr, model, chem)
.. string.format("\n%s \t\t\t %s", status:upper(), msg)
tpbat_notification = naughty.notify({
preset = { fg = beautiful.fg_normal },
text = str,
tpbat.notification = naughty.notify({
text = str,
timeout = t_out,
screen = client.focus and client.focus.screen or 1
screen = client.focus and client.focus.screen or 1
})
end
@ -89,7 +80,7 @@ function tpbat.register(args)
tpbat.bat = smapi:battery(battery) -- Create a new battery
local bat = tpbat.bat
tpbat.widget = wibox.widget.textbox('')
tpbat.widget = wibox.widget.textbox()
bat_notification_low_preset = {
title = "Battery low",
@ -117,7 +108,7 @@ function tpbat.register(args)
})
end
function update()
function tpbat.update()
bat_now = {
status = "Not present",
perc = "N/A",
@ -156,15 +147,16 @@ function tpbat.register(args)
end
widget = tpbat.widget
settings()
end
newtimer("tpbat-" .. bat.name, timeout, update)
newtimer("tpbat-" .. bat.name, timeout, tpbat.update)
widget:connect_signal('mouse::enter', function () tpbat:show() end)
widget:connect_signal('mouse::leave', function () tpbat:hide() end)
widget:connect_signal('mouse::enter', function () tpbat.show() end)
widget:connect_signal('mouse::leave', function () tpbat.hide() end)
return tpbat.widget
return tpbat
end
return setmetatable(tpbat, { __call = function(_, ...) return tpbat.register(...) end })

View File

@ -81,13 +81,11 @@ function smapi:battery(name)
local time_val = bat_now.status == 'discharging' and 'remaining_running_time' or 'remaining_charging_time'
local mins_left = self:get(time_val)
if mins_left:find("^%d+") == nil
then
return "N/A"
end
if not mins_left:find("^%d+") then return "N/A" end
local hrs = math.floor(mins_left / 60)
local min = mins_left % 60
return string.format("%02d:%02d", hrs, min)
end

View File

@ -7,16 +7,12 @@
--]]
local lines_match = require("lain.helpers").lines_match
local newtimer = require("lain.helpers").newtimer
local helpers = require("lain.helpers")
local wibox = require("wibox")
local math = { ceil = math.ceil }
local string = { format = string.format,
gmatch = string.gmatch }
local tostring = tostring
local setmetatable = setmetatable
-- CPU usage
@ -25,28 +21,26 @@ local cpu = { core = {} }
local function worker(args)
local args = args or {}
local timeout = args.timeout or 2
local settings = args.settings or function() end
local timeout = args.timeout or 2
local settings = args.settings or function() end
cpu.widget = wibox.widget.textbox('')
cpu.widget = wibox.widget.textbox()
function update()
function cpu.update()
-- Read the amount of time the CPUs have spent performing
-- different kinds of work. Read the first line of /proc/stat
-- which is the sum of all CPUs.
local times = lines_match("cpu","/proc/stat")
-- different kinds of work. Read the first line of /proc/stat
-- which is the sum of all CPUs.
local times = helpers.lines_match("cpu","/proc/stat")
for index,time in pairs(times)
do
for index,time in pairs(times) do
local coreid = index - 1
local core = cpu.core[coreid] or
{ last_active = 0 , last_total = 0, usage = 0 }
local at = 1
local idle = 0
local total = 0
local core = cpu.core[coreid] or
{ last_active = 0 , last_total = 0, usage = 0 }
local at = 1
local idle = 0
local total = 0
for field in string.gmatch(time, "[%s]+([^%s]+)")
do
for field in string.gmatch(time, "[%s]+([^%s]+)") do
-- 4 = idle, 5 = ioWait. Essentially, the CPUs have done
-- nothing during these times.
if at == 4 or at == 5 then
@ -62,27 +56,27 @@ local function worker(args)
-- Read current data and calculate relative values.
local dactive = active - core.last_active
local dtotal = total - core.last_total
local usage = math.ceil((dactive / dtotal) * 100)
local usage = math.ceil((dactive / dtotal) * 100)
core.last_active = active
core.last_total = total
core.usage = usage
-- Save current data for the next run.
cpu.core[coreid] = core;
cpu.core[coreid] = core
end
end
widget = cpu.widget
cpu_now = cpu.core
cpu_now.usage = cpu_now[0].usage
widget = cpu.widget
settings()
end
newtimer("cpu", timeout, update)
return cpu.widget
helpers.newtimer("cpu", timeout, cpu.update)
return cpu
end
return setmetatable(cpu, { __call = function(_, ...) return worker(...) end })

View File

@ -1,62 +1,50 @@
--[[
Licensed under GNU General Public License v2
* (c) 2013, Luke Bonham
* (c) 2010, Adrian C. <anrxc@sysphere.org>
* (c) 2009, Lucas de Vries <lucas@glacicle.com>
Licensed under GNU General Public License v2
* (c) 2013, Luke Bonham
--]]
local helpers = require("lain.helpers")
local beautiful = require("beautiful")
local shell = require("awful.util").shell
local focused = require("awful.screen").focused
local wibox = require("wibox")
local naughty = require("naughty")
local io = { popen = io.popen }
local pairs = pairs
local mouse = mouse
local string = { match = string.match,
format = string.format }
local string = string
local tonumber = tonumber
local setmetatable = setmetatable
-- File system disk space usage
-- lain.widgets.fs
local fs = {}
local fs_notification = nil
local fs = { unit = { ["mb"] = 1024, ["gb"] = 1024^2 } }
function fs.hide()
if fs_notification ~= nil then
naughty.destroy(fs_notification)
fs_notification = nil
end
if not fs.notification then return end
naughty.destroy(fs.notification)
fs.notification = nil
end
function fs.show(seconds, options, scr)
function fs.show(seconds, scr)
fs.update()
fs.hide()
local cmd = (options and string.format("dfs %s", options)) or "dfs"
local ws = helpers.read_pipe(helpers.scripts_dir .. cmd):gsub("\n*$", "")
if fs.followmouse then
fs.notification_preset.screen = mouse.screen
if fs.followtag then
fs.notification_preset.screen = focused()
elseif scr then
fs.notification_preset.screen = scr
fs.notification_preset.screen = scr or 1
end
fs_notification = naughty.notify({
preset = fs.notification_preset,
text = ws,
timeout = seconds or 5
fs.notification = naughty.notify({
preset = fs.notification_preset,
timeout = seconds or 5
})
end
-- Unit definitions
local unit = { ["mb"] = 1024, ["gb"] = 1024^2 }
local function worker(args)
local args = args or {}
local timeout = args.timeout or 600
@ -65,65 +53,78 @@ local function worker(args)
local notify = args.notify or "on"
local settings = args.settings or function() end
fs.followmouse = args.followmouse or false
fs.notification_preset = args.notification_preset or { fg = beautiful.fg_normal }
fs.options = args.options
fs.followtag = args.followtag or false
fs.notification_preset = args.notification_preset
fs.widget = wibox.widget.textbox('')
if not fs.notification_preset then
fs.notification_preset = {
font = "Monospace 10",
fg = "#FFFFFF",
bg = "#000000"
}
end
fs.widget = wibox.widget.textbox()
helpers.set_map(partition, false)
function update()
fs_info = {}
fs_now = {}
local f = assert(io.popen("LC_ALL=C df -kP"))
function fs.update()
fs_info, fs_now = {}, {}
helpers.async({ shell, "-c", "/usr/bin/env LC_ALL=C df -k --output=target,size,used,avail,pcent" }, function(f)
for line in string.gmatch(f, "\n[^\n]+") do
local m,s,u,a,p = string.match(line, "(/.-%s).-(%d+).-(%d+).-(%d+).-([%d]+)%%")
m = m:gsub(" ", "") -- clean target from any whitespace
for line in f:lines() do -- Match: (size) (used)(avail)(use%) (mount)
local s = string.match(line, "^.-[%s]([%d]+)")
local u,a,p = string.match(line, "([%d]+)[%D]+([%d]+)[%D]+([%d]+)%%")
local m = string.match(line, "%%[%s]([%p%w]+)")
if u and m then -- Handle 1st line and broken regexp
fs_info[m .. " size_mb"] = string.format("%.1f", tonumber(s) / unit["mb"])
fs_info[m .. " size_gb"] = string.format("%.1f", tonumber(s) / unit["gb"])
fs_info[m .. " used_p"] = tonumber(p)
fs_info[m .. " avail_p"] = 100 - tonumber(p)
fs_info[m .. " size_mb"] = string.format("%.1f", tonumber(s) / fs.unit["mb"])
fs_info[m .. " size_gb"] = string.format("%.1f", tonumber(s) / fs.unit["gb"])
fs_info[m .. " used_mb"] = string.format("%.1f", tonumber(u) / fs.unit["mb"])
fs_info[m .. " used_gb"] = string.format("%.1f", tonumber(u) / fs.unit["gb"])
fs_info[m .. " used_p"] = p
fs_info[m .. " avail_mb"] = string.format("%.1f", tonumber(a) / fs.unit["mb"])
fs_info[m .. " avail_gb"] = string.format("%.1f", tonumber(a) / fs.unit["gb"])
fs_info[m .. " avail_p"] = string.format("%d", 100 - tonumber(p))
end
end
f:close()
fs_now.size_mb = fs_info[partition .. " size_mb"] or "N/A"
fs_now.size_gb = fs_info[partition .. " size_gb"] or "N/A"
fs_now.used = fs_info[partition .. " used_p"] or "N/A"
fs_now.used_mb = fs_info[partition .. " used_mb"] or "N/A"
fs_now.used_gb = fs_info[partition .. " used_gb"] or "N/A"
fs_now.available = fs_info[partition .. " avail_p"] or "N/A"
fs_now.available_mb = fs_info[partition .. " avail_mb"] or "N/A"
fs_now.available_gb = fs_info[partition .. " avail_gb"] or "N/A"
fs_now.used = tonumber(fs_info[partition .. " used_p"]) or 0
fs_now.available = tonumber(fs_info[partition .. " avail_p"]) or 0
fs_now.size_mb = tonumber(fs_info[partition .. " size_mb"]) or 0
fs_now.size_gb = tonumber(fs_info[partition .. " size_gb"]) or 0
notification_preset = fs.notification_preset
widget = fs.widget
settings()
notification_preset = fs.notification_preset
widget = fs.widget
settings()
if notify == "on" and #fs_now.used > 0 and tonumber(fs_now.used) >= 99 and not helpers.get_map(partition) then
naughty.notify({
preset = naughty.config.presets.critical,
title = "Warning",
text = partition .. " is empty",
})
helpers.set_map(partition, true)
else
helpers.set_map(partition, false)
end
end)
if notify == "on" and fs_now.used >= 99 and not helpers.get_map(partition)
then
naughty.notify({
title = "warning",
text = partition .. " ran out!\nmake some room",
timeout = 8,
fg = "#000000",
bg = "#FFFFFF",
})
helpers.set_map(partition, true)
else
helpers.set_map(partition, false)
end
local notifycmd = (fs.options and string.format("dfs %s", fs.options)) or "dfs"
helpers.async(helpers.scripts_dir .. notifycmd, function(ws)
fs.notification_preset.text = ws:gsub("\n*$", "")
end)
end
if showpopup == "on" then
fs.widget:connect_signal('mouse::enter', function () fs:show(0) end)
fs.widget:connect_signal('mouse::leave', function () fs:hide() end)
fs.widget:connect_signal('mouse::enter', function () fs.show(0) end)
fs.widget:connect_signal('mouse::leave', function () fs.hide() end)
end
helpers.newtimer(partition, timeout, update)
helpers.newtimer(partition, timeout, fs.update)
return setmetatable(fs, { __index = fs.widget })
return fs
end
return setmetatable(fs, { __call = function(_, ...) return worker(...) end })

View File

@ -7,77 +7,71 @@
--]]
local helpers = require("lain.helpers")
local async = require("lain.asyncshell")
local naughty = require("naughty")
local wibox = require("wibox")
local mouse = mouse
local string = { format = string.format,
gsub = string.gsub }
local type = type
local tonumber = tonumber
local setmetatable = setmetatable
-- Mail IMAP check
-- lain.widgets.imap
local function worker(args)
local imap = {}
local args = args or {}
local imap = { widget = wibox.widget.textbox() }
local args = args or {}
local server = args.server
local mail = args.mail
local password = args.password
local port = args.port or 993
local timeout = args.timeout or 60
local is_plain = args.is_plain or false
local followtag = args.followtag or false
local settings = args.settings or function() end
local server = args.server
local mail = args.mail
local password = args.password
local port = args.port or 993
local timeout = args.timeout or 60
local is_plain = args.is_plain or false
local followmouse = args.followmouse or false
local settings = args.settings or function() end
local head_command = "curl --connect-timeout 3 -fsm 3"
local head_command = "curl --connect-timeout 3 -fsm 3"
local request = "-X 'SEARCH (UNSEEN)'"
if not server or not mail or not password then return end
helpers.set_map(mail, 0)
if not is_plain then
password = helpers.read_pipe(password):gsub("\n", "")
if type(password) == "string" or type(password) == "table" then
helpers.async(password, function(f) password = f:gsub("\n", "") end)
elseif type(password) == "function" then
local p = password()
end
end
imap.widget = wibox.widget.textbox('')
function update()
mail_notification_preset = {
icon = helpers.icons_dir .. "mail.png",
position = "top_left"
}
if followmouse then
mail_notification_preset.screen = mouse.screen
if followtag then
mail_notification_preset.screen = awful.screen.focused()
end
curl = string.format("%s --url imaps://%s:%s/INBOX -u %s:%q %s -k",
head_command, server, port, mail, password, request)
async.request(curl, function(f)
helpers.async(curl, function(f)
_, mailcount = string.gsub(f, "%d+", "")
_ = nil
widget = imap.widget
settings()
if mailcount >= 1 and mailcount > helpers.get_map(mail)
then
if mailcount >= 1 and mailcount > helpers.get_map(mail) then
if mailcount == 1 then
nt = mail .. " has one new message"
else
nt = mail .. " has <b>" .. mailcount .. "</b> new messages"
end
naughty.notify({
preset = mail_notification_preset,
text = nt
})
naughty.notify({ preset = mail_notification_preset, text = nt })
end
helpers.set_map(mail, mailcount)
@ -85,9 +79,9 @@ local function worker(args)
end
helpers.newtimer(mail, timeout, update, true)
imap.timer = helpers.newtimer(mail, timeout, update, true, true)
return setmetatable(imap, { __index = imap.widget })
return imap
end
return setmetatable({}, { __call = function(_, ...) return worker(...) end })

View File

@ -1,105 +0,0 @@
--[[
Licensed under GNU General Public License v2
* (c) 2013, Luke Bonham
* (c) 2010-2012, Peter Hofmann
--]]
local newtimer = require("lain.helpers").newtimer
local read_pipe = require("lain.helpers").read_pipe
local spairs = require("lain.helpers").spairs
local wibox = require("wibox")
local awful = require("awful")
local util = require("lain.util")
local io = { popen = io.popen }
local os = { getenv = os.getenv }
local pairs = pairs
local string = { len = string.len,
match = string.match }
local setmetatable = setmetatable
-- Maildir check
-- lain.widgets.maildir
local maildir = {}
local function worker(args)
local args = args or {}
local timeout = args.timeout or 60
local mailpath = args.mailpath or os.getenv("HOME") .. "/Mail"
local ignore_boxes = args.ignore_boxes or {}
local settings = args.settings or function() end
local ext_mail_cmd = args.external_mail_cmd
maildir.widget = wibox.widget.textbox('')
function update()
if ext_mail_cmd then
awful.util.spawn(ext_mail_cmd)
end
-- Find pathes to mailboxes.
local p = io.popen("find " .. mailpath ..
" -mindepth 1 -maxdepth 2 -type d" ..
" -not -name .git")
local boxes = {}
repeat
line = p:read("*l")
if line ~= nil
then
-- Find all files in the "new" subdirectory. For each
-- file, print a single character (no newline). Don't
-- match files that begin with a dot.
-- Afterwards the length of this string is the number of
-- new mails in that box.
local mailstring = read_pipe("find " .. line ..
"/new -mindepth 1 -type f " ..
"-not -name '.*' -printf a")
-- Strip off leading mailpath.
local box = string.match(line, mailpath .. "/(.*)")
local nummails = string.len(mailstring)
if nummails > 0
then
boxes[box] = nummails
end
end
until line == nil
p:close()
newmail = "no mail"
-- Count the total number of mails irrespective of where it was found
total = 0
for box, number in spairs(boxes)
do
-- Add this box only if it's not to be ignored.
if not util.element_in_table(box, ignore_boxes)
then
total = total + number
if newmail == "no mail"
then
newmail = box .. "(" .. number .. ")"
else
newmail = newmail .. ", " ..
box .. "(" .. number .. ")"
end
end
end
widget = maildir.widget
settings()
end
newtimer(mailpath, timeout, update, true)
return maildir.widget
end
return setmetatable(maildir, { __call = function(_, ...) return worker(...) end })

View File

@ -7,14 +7,11 @@
--]]
local newtimer = require("lain.helpers").newtimer
local helpers = require("lain.helpers")
local wibox = require("wibox")
local io = { lines = io.lines }
local math = { floor = math.floor }
local string = { gmatch = string.gmatch }
local gmatch = string.gmatch
local lines = io.lines
local floor = math.floor
local setmetatable = setmetatable
-- Memory usage (ignoring caches)
@ -26,25 +23,24 @@ local function worker(args)
local timeout = args.timeout or 2
local settings = args.settings or function() end
mem.widget = wibox.widget.textbox('')
mem.widget = wibox.widget.textbox()
function update()
function mem.update()
mem_now = {}
for line in io.lines("/proc/meminfo")
do
for k, v in string.gmatch(line, "([%a]+):[%s]+([%d]+).+")
do
if k == "MemTotal" then mem_now.total = math.floor(v / 1024)
elseif k == "MemFree" then mem_now.free = math.floor(v / 1024)
elseif k == "Buffers" then mem_now.buf = math.floor(v / 1024)
elseif k == "Cached" then mem_now.cache = math.floor(v / 1024)
elseif k == "SwapTotal" then mem_now.swap = math.floor(v / 1024)
elseif k == "SwapFree" then mem_now.swapf = math.floor(v / 1024)
for line in lines("/proc/meminfo") do
for k, v in gmatch(line, "([%a]+):[%s]+([%d]+).+") do
if k == "MemTotal" then mem_now.total = floor(v / 1024 + 0.5)
elseif k == "MemFree" then mem_now.free = floor(v / 1024 + 0.5)
elseif k == "Buffers" then mem_now.buf = floor(v / 1024 + 0.5)
elseif k == "Cached" then mem_now.cache = floor(v / 1024 + 0.5)
elseif k == "SwapTotal" then mem_now.swap = floor(v / 1024 + 0.5)
elseif k == "SwapFree" then mem_now.swapf = floor(v / 1024 + 0.5)
elseif k == "SReclaimable" then mem_now.srec = floor(v / 1024 + 0.5)
end
end
end
mem_now.used = mem_now.total - (mem_now.free + mem_now.buf + mem_now.cache)
mem_now.used = mem_now.total - mem_now.free - mem_now.buf - mem_now.cache - mem_now.srec
mem_now.swapused = mem_now.swap - mem_now.swapf
mem_now.perc = math.floor(mem_now.used / mem_now.total * 100)
@ -52,9 +48,9 @@ local function worker(args)
settings()
end
newtimer("mem", timeout, update)
helpers.newtimer("mem", timeout, mem.update)
return mem.widget
return mem
end
return setmetatable(mem, { __call = function(_, ...) return worker(...) end })

View File

@ -8,20 +8,15 @@
--]]
local helpers = require("lain.helpers")
local async = require("lain.asyncshell")
local shell = require("awful.util").shell
local escape_f = require("awful.util").escape
local focused = require("awful.screen").focused
local naughty = require("naughty")
local wibox = require("wibox")
local os = { execute = os.execute,
getenv = os.getenv }
local math = { floor = math.floor }
local mouse = mouse
local string = { format = string.format,
match = string.match,
gmatch = string.gmatch }
local os = { getenv = os.getenv }
local string = { format = string.format,
gmatch = string.gmatch,
match = string.match }
local setmetatable = setmetatable
-- MPD infos
@ -29,34 +24,31 @@ local setmetatable = setmetatable
local mpd = {}
local function worker(args)
local args = args or {}
local timeout = args.timeout or 2
local password = args.password or ""
local host = args.host or "127.0.0.1"
local port = args.port or "6600"
local music_dir = args.music_dir or os.getenv("HOME") .. "/Music"
local cover_size = args.cover_size or 100
local default_art = args.default_art or ""
local notify = args.notify or "on"
local followmouse = args.followmouse or false
local echo_cmd = args.echo_cmd or "echo"
local settings = args.settings or function() end
local args = args or {}
local timeout = args.timeout or 2
local password = (args.password and #args.password > 0 and string.format("password %s\\n", args.password)) or ""
local host = args.host or "127.0.0.1"
local port = args.port or "6600"
local music_dir = args.music_dir or os.getenv("HOME") .. "/Music"
local cover_pattern = args.cover_pattern or "*\\.(jpg|jpeg|png|gif)$"
local cover_size = args.cover_size or 100
local default_art = args.default_art
local notify = args.notify or "on"
local followtag = args.followtag or false
local settings = args.settings or function() end
local mpdcover = helpers.scripts_dir .. "mpdcover"
local mpdh = "telnet://" .. host .. ":" .. port
local echo = echo_cmd .. " 'password " .. password .. "\nstatus\ncurrentsong\nclose'"
local mpdh = string.format("telnet://%s:%s", host, port)
local echo = string.format("printf \"%sstatus\\ncurrentsong\\nclose\\n\"", password)
local cmd = string.format("%s | curl --connect-timeout 1 -fsm 3 %s", echo, mpdh)
mpd.widget = wibox.widget.textbox('')
mpd.widget = wibox.widget.textbox()
mpd_notification_preset = {
title = "Now playing",
timeout = 6
}
mpd_notification_preset = { title = "Now playing", timeout = 6 }
helpers.set_map("current mpd track", nil)
function mpd.update()
async.request(echo .. " | curl --connect-timeout 1 -fsm 3 " .. mpdh, function (f)
helpers.async({ shell, "-c", cmd }, function(f)
mpd_now = {
random_mode = false,
single_mode = false,
@ -70,6 +62,8 @@ local function worker(args)
artist = "N/A",
title = "N/A",
album = "N/A",
genre = "N/A",
track = "N/A",
date = "N/A",
time = "N/A",
elapsed = "N/A"
@ -83,6 +77,8 @@ local function worker(args)
elseif k == "Artist" then mpd_now.artist = escape_f(v)
elseif k == "Title" then mpd_now.title = escape_f(v)
elseif k == "Album" then mpd_now.album = escape_f(v)
elseif k == "Genre" then mpd_now.genre = escape_f(v)
elseif k == "Track" then mpd_now.track = escape_f(v)
elseif k == "Date" then mpd_now.date = escape_f(v)
elseif k == "Time" then mpd_now.time = v
elseif k == "elapsed" then mpd_now.elapsed = string.match(v, "%d+")
@ -101,41 +97,41 @@ local function worker(args)
widget = mpd.widget
settings()
if mpd_now.state == "play"
then
if notify == "on" and mpd_now.title ~= helpers.get_map("current mpd track")
then
if mpd_now.state == "play" then
if notify == "on" and mpd_now.title ~= helpers.get_map("current mpd track") then
helpers.set_map("current mpd track", mpd_now.title)
if string.match(mpd_now.file, "http.*://") == nil
then -- local file
os.execute(string.format("%s %q %q %d %q", mpdcover, music_dir,
mpd_now.file, cover_size, default_art))
current_icon = "/tmp/mpdcover.png"
else -- http stream
current_icon = default_art
if followtag then mpd_notification_preset.screen = focused() end
local common = {
preset = mpd_notification_preset,
icon = default_art,
icon_size = cover_size,
replaces_id = mpd.id
}
if not string.match(mpd_now.file, "http.*://") then -- local file instead of http stream
local path = string.format("%s/%s", music_dir, string.match(mpd_now.file, ".*/"))
local cover = string.format("find '%s' -maxdepth 1 -type f | egrep -i -m1 '%s'", path:gsub("'", "'\\''"), cover_pattern)
helpers.async({ shell, "-c", cover }, function(current_icon)
common.icon = current_icon:gsub("\n", "")
if #common.icon == 0 then common.icon = nil end
mpd.id = naughty.notify(common).id
end)
else
mpd.id = naughty.notify(common).id
end
if followmouse then
mpd_notification_preset.screen = mouse.screen
end
mpd.id = naughty.notify({
preset = mpd_notification_preset,
icon = current_icon,
replaces_id = mpd.id,
}).id
end
elseif mpd_now.state ~= "pause"
then
elseif mpd_now.state ~= "pause" then
helpers.set_map("current mpd track", nil)
end
end)
end
helpers.newtimer("mpd", timeout, mpd.update)
mpd.timer = helpers.newtimer("mpd", timeout, mpd.update, true, true)
return setmetatable(mpd, { __index = mpd.widget })
return mpd
end
return setmetatable(mpd, { __call = function(_, ...) return worker(...) end })

View File

@ -10,55 +10,40 @@
local helpers = require("lain.helpers")
local naughty = require("naughty")
local wibox = require("wibox")
local string = { format = string.format,
gsub = string.gsub,
match = string.match }
local setmetatable = setmetatable
-- Network infos
-- lain.widgets.net
local function worker(args)
local net = { last_t = 0, last_r = 0, devices = {} }
local net = { widget = wibox.widget.textbox() }
net.last_t = 0
net.last_r = 0
net.devices = {}
function net.get_first_device()
local ws = helpers.read_pipe("ip link show | cut -d' ' -f2,9")
ws = ws:match("%w+: UP") or ws:match("ppp%w+: UNKNOWN")
if ws then return { ws:match("(%w+):") }
else return {} end
end
local args = args or {}
local timeout = args.timeout or 2
local units = args.units or 1024 --kb
local notify = args.notify or "on"
local screen = args.screen or 1
local settings = args.settings or function() end
local iface = args.iface or net.get_first_device()
net.widget = wibox.widget.textbox('')
local args = args or {}
local timeout = args.timeout or 2
local units = args.units or 1024 --kb
local notify = args.notify or "on"
local screen = args.screen or 1
local settings = args.settings or function() end
-- Compatibility with old API where iface was a string corresponding to 1 interface
if type(iface) == "string" then
iftable = {iface}
else
iftable = iface
net.iface = (args.iface and (type(args.iface) == "string" and {args.iface}) or
(type(args.iface) == "table" and args.iface)) or {}
function net.get_device()
helpers.async(string.format("ip link show", device_cmd), function(ws)
ws = ws:match("(%w+): <BROADCAST,MULTICAST,.-UP,LOWER_UP>")
net.iface = ws and { ws } or {}
end)
end
-- Mark all devices as initially online/active
for i, dev in ipairs(iftable) do
helpers.set_map(dev, true)
end
if #net.iface == 0 then net.get_device() end
function update()
-- This check is required to ensure we keep looking for one device if
-- none is found by net.get_first_device() at startup (i.e. iftable = {})
if next(iftable) == nil then
iftable = net.get_first_device()
end
-- These are the totals over all specified interfaces
net_now = {
-- New api - Current state of requested devices
@ -72,15 +57,14 @@ local function worker(args)
local total_t = 0
local total_r = 0
for i, dev in ipairs(iftable) do
local dev_now = {}
for i, dev in ipairs(net.iface) do
local dev_now = {}
local dev_before = net.devices[dev] or { last_t = 0, last_r = 0 }
local now_t = tonumber(helpers.first_line(string.format("/sys/class/net/%s/statistics/tx_bytes", dev)) or 0)
local now_r = tonumber(helpers.first_line(string.format("/sys/class/net/%s/statistics/rx_bytes", dev)) or 0)
dev_now.carrier = helpers.first_line(string.format('/sys/class/net/%s/carrier', dev)) or '0'
dev_now.state = helpers.first_line(string.format('/sys/class/net/%s/operstate', dev)) or 'down'
local now_t = tonumber(helpers.first_line(string.format('/sys/class/net/%s/statistics/tx_bytes', dev)) or 0)
local now_r = tonumber(helpers.first_line(string.format('/sys/class/net/%s/statistics/rx_bytes', dev)) or 0)
dev_now.carrier = helpers.first_line(string.format("/sys/class/net/%s/carrier", dev)) or "0"
dev_now.state = helpers.first_line(string.format("/sys/class/net/%s/operstate", dev)) or "down"
dev_now.sent = (now_t - dev_before.last_t) / timeout / units
dev_now.received = (now_r - dev_before.last_r) / timeout / units
@ -88,8 +72,8 @@ local function worker(args)
net_now.sent = net_now.sent + dev_now.sent
net_now.received = net_now.received + dev_now.received
dev_now.sent = string.gsub(string.format('%.1f', dev_now.sent), ',', '.')
dev_now.received = string.gsub(string.format('%.1f', dev_now.received), ',', '.')
dev_now.sent = string.format('%.1f', dev_now.sent)
dev_now.received = string.format('%.1f', dev_now.received)
dev_now.last_t = now_t
dev_now.last_r = now_r
@ -113,32 +97,27 @@ local function worker(args)
helpers.set_map(dev, true)
end
-- Old api compatibility
net_now.carrier = dev_now.carrier
net_now.state = dev_now.state
-- And new api
net_now.devices[dev] = dev_now
-- With the new api new_now.sent and net_now.received will be the
-- new_now.sent and net_now.received will be the
-- totals across all specified devices
end
if total_t ~= net.last_t or total_r ~= net.last_r then
-- Convert to a string to round the digits after the float point
net_now.sent = string.gsub(string.format('%.1f', net_now.sent), ',', '.')
net_now.received = string.gsub(string.format('%.1f', net_now.received), ',', '.')
net.last_t = total_t
net.last_r = total_r
net_now.sent = string.format('%.1f', net_now.sent)
net_now.received = string.format('%.1f', net_now.received)
net.last_t = total_t
net.last_r = total_r
end
widget = net.widget
settings()
end
helpers.newtimer(iface, timeout, update)
helpers.newtimer("network", timeout, update)
return setmetatable(net, { __index = net.widget })
return net
end
return setmetatable({}, { __call = function(_, ...) return worker(...) end })

View File

@ -6,55 +6,57 @@
--]]
local read_pipe = require("lain.helpers").read_pipe
local newtimer = require("lain.helpers").newtimer
local helpers = require("lain.helpers")
local shell = require("awful.util").shell
local wibox = require("wibox")
local string = { gmatch = string.gmatch,
match = string.match,
format = string.format }
local setmetatable = setmetatable
-- PulseAudio volume
-- lain.widgets.pulseaudio
local pulseaudio = {}
local function worker(args)
local args = args or {}
local timeout = args.timeout or 5
local settings = args.settings or function() end
local scallback = args.scallback
local pulseaudio = { wibox.widget.textbox() }
local args = args or {}
local devicetype = args.devicetype or "sink"
local timeout = args.timeout or 5
local settings = args.settings or function() end
local scallback = args.scallback
pulseaudio.cmd = args.cmd or string.format("pacmd list-sinks | sed -n -e '0,/*/d' -e '/base volume/d' -e '/volume:/p' -e '/muted:/p' -e '/device\\.string/p'")
pulseaudio.widget = wibox.widget.textbox('')
pulseaudio.cmd = args.cmd or "pacmd list-" .. devicetype .. "s | sed -n -e '0,/*/d' -e '/base volume/d' -e '/volume:/p' -e '/muted:/p' -e '/device\\.string/p'"
function pulseaudio.update()
if scallback then pulseaudio.cmd = scallback() end
local s = read_pipe(pulseaudio.cmd)
function pulseaudio.update()
if scallback then pulseaudio.cmd = scallback() end
volume_now = {}
volume_now.index = string.match(s, "index: (%S+)") or "N/A"
volume_now.sink = string.match(s, "device.string = \"(%S+)\"") or "N/A"
volume_now.muted = string.match(s, "muted: (%S+)") or "N/A"
helpers.async({ shell, "-c", pulseaudio.cmd }, function(s)
volume_now = {
index = string.match(s, "index: (%S+)") or "N/A",
device = string.match(s, "device.string = \"(%S+)\"") or "N/A",
sink = device, -- legacy API
muted = string.match(s, "muted: (%S+)") or "N/A"
}
local ch = 1
volume_now.channel = {}
for v in string.gmatch(s, ":.-(%d+)%%") do
volume_now.channel[ch] = v
ch = ch + 1
end
local ch = 1
volume_now.channel = {}
for v in string.gmatch(s, ":.-(%d+)%%") do
volume_now.channel[ch] = v
ch = ch + 1
end
volume_now.left = volume_now.channel[1] or "N/A"
volume_now.right = volume_now.channel[2] or "N/A"
volume_now.left = volume_now.channel[1] or "N/A"
volume_now.right = volume_now.channel[2] or "N/A"
widget = pulseaudio.widget
settings()
end
widget = pulseaudio.widget
newtimer(string.format("pulseaudio-%s", timeout), timeout, pulseaudio.update)
settings()
end)
end
return setmetatable(pulseaudio, { __index = pulseaudio.widget })
helpers.newtimer("pulseaudio", timeout, pulseaudio.update)
return pulseaudio
end
return setmetatable(pulseaudio, { __call = function(_, ...) return worker(...) end })
return setmetatable({}, { __call = function(_, ...) return worker(...) end })

View File

@ -7,90 +7,32 @@
--]]
local newtimer = require("lain.helpers").newtimer
local read_pipe = require("lain.helpers").read_pipe
local helpers = require("lain.helpers")
local awful = require("awful")
local beautiful = require("beautiful")
local naughty = require("naughty")
local wibox = require("wibox")
local math = { modf = math.modf }
local mouse = mouse
local string = { format = string.format,
gmatch = string.gmatch,
match = string.match,
rep = string.rep }
local type = type
local tonumber = tonumber
local setmetatable = setmetatable
-- ALSA volume bar
-- Pulseaudio volume bar
-- lain.widgets.pulsebar
local pulsebar = {
sink = 0,
step = "1%",
colors = {
background = beautiful.bg_normal,
background = "#000000",
mute = "#EB8F8F",
unmute = "#A4CE8A"
},
mixer = "pavucontrol",
notifications = {
font = beautiful.font:sub(beautiful.font:find(""), beautiful.font:find(" ")),
font_size = "11",
color = beautiful.fg_normal,
bar_size = 18,
screen = 1
},
_current_level = 0,
_muted = false
}
function pulsebar.notify()
pulsebar.update()
local preset = {
title = "",
text = "",
timeout = 5,
screen = pulsebar.notifications.screen,
font = pulsebar.notifications.font .. " " ..
pulsebar.notifications.font_size,
fg = pulsebar.notifications.color
}
if pulsebar._muted
then
preset.title = "Sink " .. pulsebar.sink .. " - Muted"
else
preset.title = "Sink " .. pulsebar.sink .. " - " .. pulsebar._current_level .. "%"
end
int = math.modf((pulsebar._current_level / 100) * pulsebar.notifications.bar_size)
preset.text = "["
.. string.rep("|", int)
.. string.rep(" ", pulsebar.notifications.bar_size - int)
.. "]"
if pulsebar.followmouse then
preset.screen = mouse.screen
end
if pulsebar._notify ~= nil then
pulsebar._notify = naughty.notify ({
replaces_id = pulsebar._notify.id,
preset = preset,
})
else
pulsebar._notify = naughty.notify ({
preset = preset,
})
end
end
local function worker(args)
local args = args or {}
local timeout = args.timeout or 5
@ -102,79 +44,103 @@ local function worker(args)
local vertical = args.vertical or false
local scallback = args.scallback
pulsebar.cmd = args.cmd or string.format("pacmd list-sinks | sed -n -e '0,/*/d' -e '/base volume/d' -e '/volume:/p' -e '/muted:/p'")
pulsebar.colors = args.colors or pulsebar.colors
pulsebar.notifications = args.notifications or pulsebar.notifications
pulsebar.cmd = args.cmd or "pacmd list-sinks | sed -n -e '0,/*/d' -e '/base volume/d' -e '/volume:/p' -e '/muted:/p' -e '/device\\.string/p'"
pulsebar.sink = args.sink or 0
pulsebar.step = args.step or pulsebar.step
pulsebar.followmouse = args.followmouse or false
pulsebar.colors = args.colors or pulsebar.colors
pulsebar.followtag = args.followtag or false
pulsebar.notifications = args.notification_preset
pulsebar.bar = awful.widget.progressbar()
pulsebar.bar:set_background_color(pulsebar.colors.background)
pulsebar.bar:set_color(pulsebar.colors.unmute)
pulsebar.tooltip = awful.tooltip({ objects = { pulsebar.bar } })
pulsebar.bar:set_width(width)
pulsebar.bar:set_height(height)
pulsebar.bar:set_ticks(ticks)
pulsebar.bar:set_ticks_size(ticks_size)
pulsebar.bar:set_vertical(vertical)
function pulsebar.update()
if scallback then pulseaudio.cmd = scallback() end
local s = read_pipe(pulsebar.cmd)
volume_now = {}
volume_now.left = tonumber(string.match(s, ":.-(%d+)%%"))
volume_now.right = tonumber(string.match(s, ":.-(%d+)%%"))
volume_now.muted = string.match(s, "muted: (%S+)")
local volu = volume_now.left
local mute = volume_now.muted
if (volu and volu ~= pulsebar._current_level) or (mute and mute ~= pulsebar._muted)
then
pulsebar._current_level = volu
pulsebar.bar:set_value(pulsebar._current_level / 100)
if not mute and volu == 0 or mute == "yes"
then
pulsebar._muted = true
pulsebar.tooltip:set_text (" [Muted] ")
pulsebar.bar:set_color(pulsebar.colors.mute)
else
pulsebar._muted = false
pulsebar.tooltip:set_text(string.format(" %s:%s ", pulsebar.sink, volu))
pulsebar.bar:set_color(pulsebar.colors.unmute)
end
settings()
end
if not pulsebar.notification_preset then
pulsebar.notification_preset = {}
pulsebar.notification_preset.font = "Monospace 10"
end
pulsebar.bar:buttons(awful.util.table.join (
awful.button({}, 1, function()
awful.util.spawn(pulsebar.mixer)
end),
awful.button({}, 2, function()
awful.util.spawn(string.format("pactl set-sink-volume %d 100%%", pulsebar.sink))
pulsebar.update()
end),
awful.button({}, 3, function()
awful.util.spawn(string.format("pactl set-sink-mute %d toggle", pulsebar.sink))
pulsebar.update()
end),
awful.button({}, 4, function()
awful.util.spawn(string.format("pactl set-sink-volume %d +%s", pulsebar.sink, pulsebar.step))
pulsebar.update()
end),
awful.button({}, 5, function()
awful.util.spawn(string.format("pactl set-sink-volume %d -%s", pulsebar.sink, pulsebar.step))
pulsebar.update()
end)
))
pulsebar.bar = wibox.widget {
forced_height = height,
forced_width = width,
color = pulsebar.colors.unmute,
background_color = pulsebar.colors.background,
margins = 1,
paddings = 1,
ticks = ticks,
ticks_size = ticks_size,
widget = wibox.widget.progressbar,
layout = vertical and wibox.container.rotate
}
timer_id = string.format("pulsebar-%s", pulsebar.sink)
pulsebar.tooltip = awful.tooltip({ objects = { pulsebar.bar } })
newtimer(timer_id, timeout, pulsebar.update)
function pulsebar.update(callback)
if scallback then pulseaudio.cmd = scallback() end
helpers.async({ awful.util.shell, "-c", pulsebar.cmd }, function(s)
volume_now = {
index = string.match(s, "index: (%S+)") or "N/A",
sink = string.match(s, "device.string = \"(%S+)\"") or "N/A",
muted = string.match(s, "muted: (%S+)") or "N/A"
}
local ch = 1
volume_now.channel = {}
for v in string.gmatch(s, ":.-(%d+)%%") do
volume_now.channel[ch] = v
ch = ch + 1
end
volume_now.left = volume_now.channel[1] or "N/A"
volume_now.right = volume_now.channel[2] or "N/A"
local volu = volume_now.left
local mute = volume_now.muted
if (volu and volu ~= pulsebar._current_level) or (mute and mute ~= pulsebar._muted) then
pulsebar._current_level = volu
pulsebar.bar:set_value(pulsebar._current_level / 100)
if (not mute and volu == 0) or mute == "yes" then
pulsebar._muted = true
pulsebar.tooltip:set_text ("[Muted]")
pulsebar.bar.color = pulsebar.colors.mute
else
pulsebar._muted = false
pulsebar.tooltip:set_text(string.format("%s: %s", pulsebar.sink, volu))
pulsebar.bar.color = pulsebar.colors.unmute
end
settings()
if type(callback) == "function" then callback() end
end
end)
end
function pulsebar.notify()
pulsebar.update(function()
local preset = pulsebar.notification_preset
if pulsebar._muted then
preset.title = string.format("Sink %s - Muted", pulsebar.sink)
else
preset.title = string.format("%s - %s%%", pulsebar.sink, pulsebar._current_level)
end
int = math.modf((pulsebar._current_level / 100) * awful.screen.focused().mywibox.height)
preset.text = string.format("[%s%s]", string.rep("|", int),
string.rep(" ", awful.screen.focused().mywibox.height - int))
if pulsebar.followtag then preset.screen = awful.screen.focused() end
if not pulsebar.notification then
pulsebar.notification = naughty.notify {
preset = preset,
destroy = function() pulsebar.notification = nil end
}
else
naughty.replace_text(pulsebar.notification, preset.title, preset.text)
end
end)
end
helpers.newtimer(string.format("pulsebar-%s", pulsebar.sink), timeout, pulsebar.update)
return pulsebar
end

View File

@ -7,13 +7,10 @@
--]]
local newtimer = require("lain.helpers").newtimer
local helpers = require("lain.helpers")
local wibox = require("wibox")
local io = { open = io.open }
local string = { match = string.match }
local setmetatable = setmetatable
-- System load
@ -21,13 +18,13 @@ local setmetatable = setmetatable
local sysload = {}
local function worker(args)
local args = args or {}
local timeout = args.timeout or 2
local args = args or {}
local timeout = args.timeout or 2
local settings = args.settings or function() end
sysload.widget = wibox.widget.textbox('')
sysload.widget = wibox.widget.textbox()
function update()
function sysload.update()
local f = io.open("/proc/loadavg")
local ret = f:read("*all")
f:close()
@ -38,8 +35,9 @@ local function worker(args)
settings()
end
newtimer("sysload", timeout, update)
return sysload.widget
helpers.newtimer("sysload", timeout, sysload.update)
return sysload
end
return setmetatable(sysload, { __call = function(_, ...) return worker(...) end })

View File

@ -6,13 +6,10 @@
--]]
local newtimer = require("lain.helpers").newtimer
local helpers = require("lain.helpers")
local wibox = require("wibox")
local io = { open = io.open }
local tonumber = tonumber
local setmetatable = setmetatable
-- coretemp
@ -25,9 +22,9 @@ local function worker(args)
local tempfile = args.tempfile or "/sys/class/thermal/thermal_zone0/temp"
local settings = args.settings or function() end
temp.widget = wibox.widget.textbox('')
temp.widget = wibox.widget.textbox()
function update()
function temp.update()
local f = io.open(tempfile)
if f then
coretemp_now = tonumber(f:read("*all")) / 1000
@ -40,9 +37,9 @@ local function worker(args)
settings()
end
newtimer("coretemp", timeout, update)
helpers.newtimer("coretemp", timeout, temp.update)
return temp.widget
return temp
end
return setmetatable(temp, { __call = function(_, ...) return worker(...) end })

View File

@ -6,24 +6,17 @@
--]]
local newtimer = require("lain.helpers").newtimer
local read_pipe = require("lain.helpers").read_pipe
local async = require("lain.asyncshell")
local helpers = require("lain.helpers")
local json = require("lain.util").dkjson
local lain_icons = require("lain.helpers").icons_dir
local focused = require("awful.screen").focused
local naughty = require("naughty")
local wibox = require("wibox")
local math = { floor = math.floor }
local os = { time = os.time,
date = os.date,
difftime = os.difftime }
local string = { format = string.format,
gsub = string.gsub }
local mouse = mouse
local tonumber = tonumber
local setmetatable = setmetatable
@ -32,7 +25,7 @@ local setmetatable = setmetatable
-- lain.widgets.weather
local function worker(args)
local weather = {}
local weather = { widget = wibox.widget.textbox() }
local args = args or {}
local APPID = args.APPID or "3e321f9414eaedbfab34983bda77a66e" -- lain default
local timeout = args.timeout or 900 -- 15 min
@ -49,32 +42,33 @@ local function worker(args)
local lang = args.lang or "en"
local cnt = args.cnt or 5
local date_cmd = args.date_cmd or "date -u -d @%d +'%%a %%d'"
local icons_path = args.icons_path or lain_icons .. "openweathermap/"
local icons_path = args.icons_path or helpers.icons_dir .. "openweathermap/"
local notification_preset = args.notification_preset or {}
local notification_text_fun = args.notification_text_fun or
function (wn)
local day = string.gsub(read_pipe(string.format(date_cmd, wn["dt"])), "\n", "")
local day = os.date("%a %d", wn["dt"])
local tmin = math.floor(wn["temp"]["min"])
local tmax = math.floor(wn["temp"]["max"])
local desc = wn["weather"][1]["description"]
return string.format("<b>%s</b>: %s, %d - %d ", day, desc, tmin, tmax)
end
local weather_na_markup = args.weather_na_markup or " N/A "
local followmouse = args.followmouse or false
local followtag = args.followtag or false
local settings = args.settings or function() end
weather.widget = wibox.widget.textbox(weather_na_markup)
weather.widget:set_markup(weather_na_markup)
weather.icon_path = icons_path .. "na.png"
weather.icon = wibox.widget.imagebox(weather.icon_path)
weather.icon = wibox.widget.imagebox(weather.icon_path)
function weather.show(t_out)
weather.hide()
if followmouse then
notification_preset.screen = mouse.screen
if followtag then
notification_preset.screen = focused()
end
if not weather.notification_text then
weather.update()
weather.forecast_update()
end
@ -104,7 +98,7 @@ local function worker(args)
function weather.forecast_update()
local cmd = string.format(forecast_call, city_id, units, lang, cnt, APPID)
async.request(cmd, function(f)
helpers.async(cmd, function(f)
local pos, err
weather_now, pos, err = json.decode(f, 1, nil)
@ -124,7 +118,7 @@ local function worker(args)
function weather.update()
local cmd = string.format(current_call, city_id, units, lang, APPID)
async.request(cmd, function(f)
helpers.async(cmd, function(f)
local pos, err, icon
weather_now, pos, err = json.decode(f, 1, nil)
@ -134,12 +128,9 @@ local function worker(args)
local sunrise = tonumber(weather_now["sys"]["sunrise"])
local sunset = tonumber(weather_now["sys"]["sunset"])
local icon = weather_now["weather"][1]["icon"]
local utc_m = string.gsub(read_pipe(string.format("date -u -d 'today 00:00:00' +'%%s'")), "\n", "")
local loc_m = string.gsub(read_pipe(string.format("date -d 'today 00:00:00' +'%%s'")), "\n", "")
loc_m = tonumber(loc_m)
utc_m = tonumber(utc_m)
offset = utc_offset()
local loc_m = os.time { year = os.date("%Y"), month = os.date("%m"), day = os.date("%d"), hour = 0 }
local offset = utc_offset()
local utc_m = loc_m + offset
-- if we are 1 day after the GMT, return 1 day back, and viceversa
if offset > 0 and loc_m >= utc_m then
@ -168,10 +159,10 @@ local function worker(args)
weather.attach(weather.widget)
newtimer("weather-" .. city_id, timeout, weather.update)
newtimer("weather_forecast-" .. city_id, timeout, weather.forecast_update)
weather.timer = helpers.newtimer("weather-" .. city_id, timeout, weather.update, false, true)
weather.timer_forecast = helpers.newtimer("weather_forecast-" .. city_id, timeout, weather.forecast_update, false, true)
return setmetatable(weather, { __index = weather.widget })
return weather
end
return setmetatable({}, { __call = function(_, ...) return worker(...) end })

2
wiki

@ -1 +1 @@
Subproject commit d0df450d05655c5d8f724c42dc6b5d18b3676a60
Subproject commit af671ad9bb1ce9c7bb74a75f489a3b5d0a934558