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 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> :Author: Luke Bonham <dada [at] archlinux [dot] info>
:Version: git :Version: git
:License: GNU-GPL2_ :License: GNU-GPL2_
:Source: https://github.com/copycat-killer/lain :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 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. 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 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! - 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 .. image:: http://i.imgur.com/STCPcaJ.png
.. _GNU-GPL2: http://www.gnu.org/licenses/gpl-2.0.html .. _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-vain: https://github.com/vain/awesome-vain
.. _Awesome: https://github.com/awesomeWM/awesome .. _Awesome: https://github.com/awesomeWM/awesome
.. _wiki: https://github.com/copycat-killer/lain/wiki .. _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 easy_async = require("awful.spawn").easy_async
local capi = { timer = (type(timer) == 'table' and timer or require ("gears.timer")) } local timer = require("gears.timer")
local io = { open = io.open, local debug = require("debug")
lines = io.lines, local io = { lines = io.lines,
popen = io.popen } open = io.open }
local rawget = rawget local rawget = rawget
local table = { sort = table.sort } local table = { sort = table.sort }
-- Lain helper functions for internal use -- Lain helper functions for internal use
-- lain.helpers -- lain.helpers
@ -90,38 +89,33 @@ end
helpers.timer_table = {} helpers.timer_table = {}
function helpers.newtimer(_name, timeout, fun, nostart) function helpers.newtimer(name, timeout, fun, nostart, stoppable)
local name = timeout if not name or #name == 0 then return end
name = (stoppable and name) or timeout
if not helpers.timer_table[name] then 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() helpers.timer_table[name]:start()
end end
helpers.timer_table[name]:connect_signal("timeout", fun) helpers.timer_table[name]:connect_signal("timeout", fun)
if not nostart then if not nostart then
helpers.timer_table[name]:emit_signal("timeout") helpers.timer_table[name]:emit_signal("timeout")
end end
return stoppable and helpers.timer_table[name]
end end
-- }}} -- }}}
-- {{{ Pipe operations -- {{{ Pipe operations
-- read the full output of a command output -- run a command and execute a function on its output (asynchronous pipe)
function helpers.read_pipe(cmd) -- @param cmd the input command
local f = assert(io.popen(cmd)) -- @param callback function to execute on cmd output
local output = f:read("*all") -- @return cmd PID
f:close() function helpers.async(cmd, callback)
return output return easy_async(cmd,
end function (stdout, stderr, reason, exit_code)
callback(stdout)
-- return line iterator of a command output end)
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
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) function helpers.spairs(t)
-- collect the keys -- collect the keys
local keys = {} local keys = {}
@ -157,7 +163,7 @@ function helpers.spairs(t)
end end
end end
end end
--}}}
-- }}}
return helpers 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 return {
local lain =
{
layout = require("lain.layout"), layout = require("lain.layout"),
util = require("lain.util"), util = require("lain.util"),
widgets = require("lain.widgets") widgets = require("lain.widgets")
} }
return lain

View File

@ -7,19 +7,17 @@ source = {
description = { description = {
summary = "Layout, widgets and utilities for Awesome WM", summary = "Layout, widgets and utilities for Awesome WM",
detailed = [[ 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", homepage = "https://github.com/copycat-killer/lain",
license = "GPL v2" license = "GPL v2"
} }
dependencies = { dependencies = {
"lua >= 5.1", "lua >= 5.3",
"awesome >= 3.5", "awesome >= 4.0",
"alsa-utils", "curl"
"curl",
"imagemagick"
} }
supported_platforms = { "linux" } supported_platforms = { "linux" }
build = { build = {

View File

@ -8,72 +8,166 @@
--]] --]]
local tag = require("awful.tag") local floor = math.floor
local beautiful = require("beautiful") local screen = screen
local cascade = local cascade = {
{
name = "cascade", name = "cascade",
nmaster = 0, nmaster = 0,
offset_x = 32, 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) local function do_cascade(p, tiling)
local t = p.tag or screen[p.screen].selected_tag
-- 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 wa = p.workarea local wa = p.workarea
local cls = p.clients local cls = p.clients
wa.height = wa.height - (global_border * 2) if #cls == 0 then return end
wa.width = wa.width - (global_border * 2)
wa.x = wa.x + global_border
wa.y = wa.y + global_border
-- Opening a new window will usually force all existing windows to if not tiling then
-- get resized. This wastes a lot of CPU time. So let's set a lower -- Cascade windows.
-- bound to "how_many": This wastes a little screen space but you'll
-- get a much better user experience. local num_c
local t = tag.selected(p.screen) if cascade.nmaster > 0 then
local num_c num_c = cascade.nmaster
if cascade.nmaster > 0 else
then num_c = t.master_count
num_c = cascade.nmaster 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 else
num_c = tag.getnmaster(t) -- Layout with one fixed column meant for a master window. Its
end -- width is calculated according to mwfact. Other clients are
-- cascaded or "tabbed" in a slave column on the right.
local how_many = #cls -- (1) (2) (3) (4)
if how_many < num_c -- +----------+---+ +----------+---+ +----------+---+ +----------+---+
then -- | | | | | 3 | | | 4 | | +---+|
how_many = num_c -- | | | -> | | | -> | +---++ -> | +---+|+
end -- | 1 | 2 | | 1 +---++ | 1 | 3 || | 1 +---+|+|
-- | | | | | 2 || | +---++| | +---+|+ |
-- | | | | | || | | 2 | | | | 2 |+ |
-- +----------+---+ +---------+---++ +--------+---+-+ +------+---+---+
local current_offset_x = cascade.offset_x * (how_many - 1) local mwfact
local current_offset_y = cascade.offset_y * (how_many - 1) if cascade.tile.mwfact > 0 then
mwfact = cascade.tile.mwfact
else
mwfact = t.master_width_factor
end
-- Iterate. -- Make slave windows overlap main window? Do this if ncol is 1.
for i = 1,#cls,1 local overlap_main
do if cascade.tile.ncol > 0 then
local c = cls[i] 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 = {} 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 if overlap_main == 1 then
g.y = wa.y + (i - 1) * cascade.offset_y g.width = wa.width
g.width = wa.width - current_offset_x - 2*c.border_width
g.height = wa.height - current_offset_y - 2*c.border_width -- The size of the main window may be reduced a little bit.
if g.width < 1 then g.width = 1 end -- 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 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
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 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 Licensed under GNU General Public License v2
* (c) 2016, Henrik Antonsson
* (c) 2015, Joerg Jaspert
* (c) 2014, projektile * (c) 2014, projektile
* (c) 2013, Luke Bonham * (c) 2013, Luke Bonham
* (c) 2010-2012, Peter Hofmann * (c) 2010-2012, Peter Hofmann
--]] --]]
local awful = require("awful") local floor = math.floor
local beautiful = require("beautiful") local screen = screen
local tonumber = tonumber
local math = { floor = math.floor }
local centerwork = local centerwork = {
{
name = "centerwork", name = "centerwork",
top_right = 0, horizontal = { name = "centerworkh" }
bottom_right = 1,
bottom_left = 2,
top_left = 3
} }
function centerwork.arrange(p) local function do_centerwork(p, orientation)
-- A useless gap (like the dwm patch) can be defined with local t = p.tag or screen[p.screen].selected_tag
-- 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 wa = p.workarea
local cls = p.clients local cls = p.clients
-- Borders are factored in. if #cls == 0 then return end
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 c = cls[1]
local t = awful.tag.selected(p.screen) local g = {}
local mwfact = awful.tag.getmwfact(t)
if #cls > 0 -- Main column, fixed width and height.
then local mwfact = t.master_width_factor
-- Main column, fixed width and height. local mainhei = floor(wa.height * mwfact)
local c = cls[1] local mainwid = floor(wa.width * mwfact)
local g = {} local slavewid = wa.width - mainwid
local mainwid = math.floor(wa.width * mwfact) local slaveLwid = floor(slavewid / 2)
local slavewid = wa.width - mainwid local slaveRwid = slavewid - slaveLwid
local slaveLwid = math.floor(slavewid / 2) local slavehei = wa.height - mainhei
local slaveRwid = slavewid - slaveLwid local slaveThei = floor(slavehei / 2)
local slaveThei = math.floor(wa.height / 2) local slaveBhei = slavehei - slaveThei
local slaveBhei = wa.height - slaveThei local nbrFirstSlaves = floor(#cls / 2)
local Thalfgap = math.floor(useless_gap / 2) local nbrSecondSlaves = floor((#cls - 1) / 2)
local Bhalfgap = useless_gap - Thalfgap
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.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 g.height = mainhei
if g.height < 1 then g.height = 1 end g.width = wa.width
c:geometry(g)
-- Auxiliary windows. g.x = wa.x
if #cls > 1 g.y = wa.y + slaveThei
then end
local at = 0
for i = 2,#cls if g.width < 1 then g.width = 1 end
do if g.height < 1 then g.height = 1 end
-- It's all fixed. If there are more than 5 clients,
-- those additional clients will float. This is p.geometries[c] = g
-- intentional.
if at == 4 -- Auxiliary windows.
then if #cls <= 1 then return end
break 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 end
else
-- right slave
g.x = wa.x + slaveLwid + mainwid
g.y = wa.y + (rowIndex-1)*slaveSecondDim
c = cls[i] g.width = slaveRwid
g = {}
if i - 2 == centerwork.top_left -- if last slave in right row use remaining space for that slave
then if rowIndex == nbrSecondSlaves then
-- top left g.height = wa.y + wa.height - g.y
g.x = wa.x + useless_gap else
g.y = wa.y + useless_gap g.height = slaveSecondDim
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
end 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 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
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
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 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 Licensed under GNU General Public License v2
* (c) 2014, projektile * (c) 2014, projektile
* (c) 2013, Luke Bonham * (c) 2013, Luke Bonham
* (c) 2010, Nicolas Estibals
* (c) 2010-2012, Peter Hofmann * (c) 2010-2012, Peter Hofmann
--]] --]]
local tag = require("awful.tag") local math = { ceil = math.ceil,
local beautiful = require("beautiful") floor = math.floor,
local math = { ceil = math.ceil, max = math.max }
floor = math.floor, local screen = screen
max = math.max } local tonumber = tonumber
local tonumber = tonumber
local termfair = { name = "termfair" } local termfair = { name = "termfair" }
termfair.center = { name = "centerfair" }
function termfair.arrange(p) local function do_fair(p, orientation)
-- Layout with fixed number of vertical columns (read from nmaster). local t = p.tag or screen[p.screen].selected_tag
-- 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 wa = p.workarea local wa = p.workarea
local cls = p.clients local cls = p.clients
-- Borders are factored in. if #cls == 0 then return end
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? if orientation == "west" then
local t = tag.selected(p.screen) -- Layout with fixed number of vertical columns (read from nmaster).
local num_x = termfair.nmaster or tag.getnmaster(t) -- 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. -- (1) (2) (3)
local desired_y = termfair.ncol or tag.getncol(t) -- +---+---+---+ +---+---+---+ +---+---+---+
-- | | | | | | | | | | | |
-- | 1 | | | -> | 2 | 1 | | -> | 3 | 2 | 1 | ->
-- | | | | | | | | | | | |
-- +---+---+---+ +---+---+---+ +---+---+---+
if #cls > 0 -- (4) (5) (6)
then -- +---+---+---+ +---+---+---+ +---+---+---+
local num_y = math.max(math.ceil(#cls / num_x), desired_y) -- | 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 cur_num_x = num_x
local at_x = 0 local at_x = 0
local at_y = 0 local at_y = 0
local remaining_clients = #cls 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 -- We start the first row. Left-align by limiting the number of
-- available slots. -- available slots.
if remaining_clients < num_x if remaining_clients < num_x then
then
cur_num_x = remaining_clients cur_num_x = remaining_clients
end end
-- Iterate in reversed order. -- Iterate in reversed order.
for i = #cls,1,-1 for i = #cls,1,-1 do
do
-- Get x and y position. -- Get x and y position.
local c = cls[i] local c = cls[i]
local this_x = cur_num_x - at_x - 1 local this_x = cur_num_x - at_x - 1
local this_y = num_y - at_y - 1 local this_y = num_y - at_y - 1
-- Calc geometry. -- Calculate geometry.
local g = {} local g = {}
if this_x == (num_x - 1) if this_x == (num_x - 1) then
then g.width = wa.width - (num_x - 1)*width
g.width = wa.width - (num_x - 1)*width - (num_x + 1)*useless_gap - 2*c.border_width
else else
g.width = width - 2*c.border_width g.width = width
end end
if this_y == (num_y - 1)
then if this_y == (num_y - 1) then
g.height = wa.height - (num_y - 1)*height - (num_y + 1)*useless_gap - 2*c.border_width g.height = wa.height - (num_y - 1)*height
else else
g.height = height - 2*c.border_width g.height = height
end end
g.x = wa.x + this_x*width g.x = wa.x + this_x*width
g.y = wa.y + this_y*height g.y = wa.y + this_y*height
if useless_gap > 0 if g.width < 1 then g.width = 1 end
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.height < 1 then g.height = 1 end if g.height < 1 then g.height = 1 end
c:geometry(g)
p.geometries[c] = g
remaining_clients = remaining_clients - 1 remaining_clients = remaining_clients - 1
-- Next grid position. -- Next grid position.
at_x = at_x + 1 at_x = at_x + 1
if at_x == num_x if at_x == num_x then
then
-- Row full, create a new one above it. -- Row full, create a new one above it.
at_x = 0 at_x = 0
at_y = at_y + 1 at_y = at_y + 1
-- We start a new row. Left-align. -- We start a new row. Left-align.
if remaining_clients < num_x if remaining_clients < num_x then
then
cur_num_x = remaining_clients cur_num_x = remaining_clients
end end
end 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
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 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) # Adapted from Eridan's "fs" (cleanup, enhancements and switch to bash/Linux)
# JM, 10/12/2004 # 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 awful = require("awful")
local beautiful = require("beautiful") local sqrt = math.sqrt
local math = { sqrt = math.sqrt }
local mouse = mouse
local pairs = pairs local pairs = pairs
local string = { gsub = string.gsub }
local client = client local client = client
local screen = screen
local tonumber = tonumber local tonumber = tonumber
local wrequire = require("lain.helpers").wrequire local wrequire = require("lain.helpers").wrequire
local setmetatable = setmetatable local setmetatable = setmetatable
@ -29,26 +24,23 @@ local setmetatable = setmetatable
-- lain.util -- lain.util
local util = { _NAME = "lain.util" } local util = { _NAME = "lain.util" }
-- Like awful.menu.clients, but only show clients of currently selected -- Like awful.menu.clients, but only show clients of currently selected tags
-- tags.
function util.menu_clients_current_tags(menu, args) function util.menu_clients_current_tags(menu, args)
-- List of currently selected tags. -- 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. -- Final list of menu items.
local cls_t = {} 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 -- 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. -- the menu. A click on a menu item will raise that client.
for i = 1,#cls_tags for i = 1,#cls_tags do
do local t = cls_tags[i]
local t = cls_tags[i]
local cls = t:clients() local cls = t:clients()
for k, c in pairs(cls) for k, c in pairs(cls) do
do
cls_t[#cls_t + 1] = { awful.util.escape(c.name) or "", cls_t[#cls_t + 1] = { awful.util.escape(c.name) or "",
function () function ()
c.minimized = false c.minimized = false
@ -68,167 +60,109 @@ function util.menu_clients_current_tags(menu, args)
-- Set the list of items and show the menu. -- Set the list of items and show the menu.
menu.items = cls_t menu.items = cls_t
local m = awful.menu.new(menu) local m = awful.menu(menu)
m:show(args) m:show(args)
return m return m
end end
-- Magnify a client: Set it to "float" and resize it. -- Magnify a client: set it to "float" and resize it.
local magnified_client = nil
function util.magnify_client(c) 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) util.mc(c)
magnified_client = c util.magnified_client = c
else else
awful.client.floating.set(c, false) c.floating = false
magnified_client = nil util.magnified_client = nil
end end
end end
-- https://github.com/copycat-killer/lain/issues/195 -- https://github.com/copycat-killer/lain/issues/195
function util.mc(c) function util.mc(c)
c = c or magnified_client c = c or util.magnified_client
if not c then return end if not c then return end
awful.client.floating.set(c, true)
local mg = screen[mouse.screen].geometry c.floating = true
local tag = awful.tag.selected(mouse.screen) local s = awful.screen.focused()
local mwfact = awful.tag.getmwfact(tag) local mg = s.geometry
local g = {} local mwfact = s.selected_tag.master_width_factor or 0.5
g.width = math.sqrt(mwfact) * mg.width local g = {}
g.height = math.sqrt(mwfact) * mg.height g.width = sqrt(mwfact) * mg.width
g.x = mg.x + (mg.width - g.width) / 2 g.height = sqrt(mwfact) * mg.height
g.y = mg.y + (mg.height - g.height) / 2 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 if c then c:geometry(g) end -- if c is still a valid object
end 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 -- Non-empty tag browsing
-- direction in {-1, 1} <-> {previous, next} non-empty tag -- direction in {-1, 1} <-> {previous, next} non-empty tag
function util.tag_view_nonempty(direction, sc) function util.tag_view_nonempty(direction, sc)
local s = sc or mouse.screen or 1 local s = sc or awful.screen.focused()
local scr = screen[s]
for i = 1, #awful.tag.gettags(s) do for i = 1, #s.tags do
awful.tag.viewidx(direction,s) awful.tag.viewidx(direction, s)
if #awful.client.visible(s) > 0 then if #s.clients > 0 then
return return
end end
end end
end end
-- {{{ Dynamic tagging -- {{{ Dynamic tagging
--
-- Add a new tag -- Add a new tag
function util.add_tag(mypromptbox) function util.add_tag()
awful.prompt.run({prompt="New tag name: "}, mypromptbox[mouse.screen].widget, awful.prompt.run {
function(text) prompt = "New tag name: ",
if text:len() > 0 then textbox = awful.screen.focused().mypromptbox.widget,
props = { selected = true } exe_callback = function(name)
tag = awful.tag.add(new_name, props) if not name or #name == 0 then return end
tag.name = text awful.tag.add(name, { screen = awful.screen.focused() }):view_only()
tag:emit_signal("property::name")
end end
end) }
end end
-- Rename current tag -- Rename current tag
-- @author: minism function util.rename_tag()
function util.rename_tag(mypromptbox) awful.prompt.run {
local tag = awful.tag.selected(mouse.screen) prompt = "Rename tag: ",
awful.prompt.run({prompt="Rename tag: "}, mypromptbox[mouse.screen].widget, textbox = awful.screen.focused().mypromptbox.widget,
function(text) exe_callback = function(new_name)
if text:len() > 0 then if not new_name or #new_name == 0 then return end
tag.name = text local t = awful.screen.focused().selected_tag
tag:emit_signal("property::name") if t then
t.name = new_name
end
end end
end) }
end end
-- Move current tag -- Move current tag
-- pos in {-1, 1} <-> {previous, next} tag position -- pos in {-1, 1} <-> {previous, next} tag position
function util.move_tag(pos) function util.move_tag(pos)
local tag = awful.tag.selected(mouse.screen) local tag = awful.screen.focused().selected_tag
local idx = awful.tag.getidx(tag)
if tonumber(pos) <= -1 then if tonumber(pos) <= -1 then
awful.tag.move(idx - 1, tag) awful.tag.move(tag.index - 1, tag)
else else
awful.tag.move(idx + 1, tag) awful.tag.move(tag.index + 1, tag)
end end
end end
-- Remove current tag (if empty) -- Delete current tag
-- Any rule set on the tag shall be broken -- Any rule set on the tag shall be broken
function util.remove_tag() function util.delete_tag()
local tag = awful.tag.selected(mouse.screen) local t = awful.screen.focused().selected_tag
local prevtag = awful.tag.gettags(mouse.screen)[awful.tag.getidx(tag) - 1] if not t then return end
awful.tag.delete(tag, prevtag) t:delete()
end end
--
-- }}} -- }}}
-- On the fly useless gaps change -- On the fly useless gaps change
function util.useless_gaps_resize(thatmuch) function util.useless_gaps_resize(thatmuch)
beautiful.useless_gap_width = tonumber(beautiful.useless_gap_width) + thatmuch local scr = awful.screen.focused()
awful.layout.arrange(mouse.screen) scr.selected_tag.gap = scr.selected_tag.gap + tonumber(thatmuch)
end awful.layout.arrange(scr)
-- 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
end end
return setmetatable(util, { __index = wrequire }) return setmetatable(util, { __index = wrequire })

View File

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

View File

@ -3,22 +3,22 @@
Licensed under GNU General Public License v2 Licensed under GNU General Public License v2
* (c) 2016, Luke Bonham * (c) 2016, Luke Bonham
* (c) 2015, unknown
--]] --]]
local awful = require("awful") local awful = require("awful")
local capi = { client = client, local capi = { client = client }
mouse = mouse,
screen = screen, local math = { floor = math.floor }
timer = timer } local string = { format = string.format }
local string = string
local pairs = pairs local pairs = pairs
local screen = screen
local setmetatable = setmetatable local setmetatable = setmetatable
local tostring = tostring
-- Quake-like Dropdown application spawn -- Quake-like Dropdown application spawn
-- Original version: https://awesomewm.org/wiki/Drop-down_terminal#Another_solution
local quake = {} local quake = {}
-- If you have a rule like "awful.client.setslave" for your terminals, -- If you have a rule like "awful.client.setslave" for your terminals,
@ -26,137 +26,144 @@ local quake = {}
-- run into problems with focus. -- run into problems with focus.
function quake:display() function quake:display()
-- First, we locate the client if self.followtag then self.screen = awful.screen.focused() end
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 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 if not client and not self.visible then return end
-- 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
-- Resize if not client then
awful.client.floating.set(client, true) -- The client does not exist, we spawn it
client.border_width = self.border cmd = string.format("%s %s %s", self.app,
client.size_hints_honor = false string.format(self.argname, self.name), self.extra)
if self.notexist then awful.spawn(cmd, { tag = self.screen.selected_tag })
client:geometry(self.geometry) return
self.notexist = false end
end
-- Not sticky and on top -- Set geometry
client.ontop = true client.floating = true
client.above = true client.border_width = self.border
client.skip_taskbar = true client.size_hints_honor = false
client.sticky = false client:geometry(self:compute_size())
-- Toggle display -- Set not sticky and on top
if self.visible then client.sticky = false
client.hidden = false client.ontop = true
client:raise() client.above = true
self.last_tag = tostring(awful.tag.selected(self.screen)) client.skip_taskbar = true
client:tags({awful.tag.selected(self.screen)})
capi.client.focus = client -- 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 else
client.hidden = true client.hidden = true
local ctags = client:tags() local ctags = client:tags()
for i, t in pairs(ctags) do for i, t in pairs(ctags) do
ctags[i] = nil ctags[i] = nil
end end
client:tags(ctags) client:tags(ctags)
end 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 end
function quake:new(config) function quake:new(config)
local conf = config or {} local conf = config or {}
conf.app = conf.app or "xterm" -- application to spawn conf.app = conf.app or "xterm" -- application to spawn
conf.name = conf.name or "QuakeDD" -- window name conf.name = conf.name or "QuakeDD" -- window name
conf.argname = conf.argname or "-name %s" -- how to specify window name conf.argname = conf.argname or "-name %s" -- how to specify window name
conf.extra = conf.extra or "" -- extra arguments conf.extra = conf.extra or "" -- extra arguments
conf.visible = conf.visible or false -- initially not visible conf.border = conf.border or 1 -- client border width
conf.screen = conf.screen or capi.mouse.screen conf.visible = conf.visible or false -- initially not visible
conf.border = conf.border or 1 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 -- If width or height <= 1 this is a proportion of the workspace
wibox_height = conf.wibox_height or 18 -- statusbar weight conf.height = conf.height or 0.25 -- height
height = conf.height or 0.25 -- height conf.width = conf.width or 1 -- width
width = conf.width or 1 -- width conf.vert = conf.vert or "top" -- top, bottom or center
vert = conf.vert or "top" -- top, bottom or center conf.horiz = conf.horiz or "left" -- left, right or center
horiz = conf.horiz or "center" -- left, right or center conf.geometry = {} -- internal use
-- Compute size local dropdown = setmetatable(conf, { __index = quake })
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 console = setmetatable(conf, { __index = quake }) capi.client.connect_signal("manage", function(c)
capi.client.connect_signal("manage", function(c) if c.instance == dropdown.name and c.screen == dropdown.screen then
if c.instance == console.name and c.screen == console.screen then dropdown:display()
console:display() end
end
end)
capi.client.connect_signal("unmanage", function(c)
if c.instance == console.name and c.screen == console.screen then
console.visible = false
end
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. return dropdown
local reattach = capi.timer { timeout = 0 }
reattach:connect_signal("timeout", function()
reattach:stop()
console:display()
end)
reattach:start()
return console
end end
function quake:toggle() function quake:toggle()
current_tag = awful.tag.selected(self.screen) if self.followtag then self.screen = awful.screen.focused() end
if self.last_tag ~= tostring(current_tag) and self.visible then local current_tag = self.screen.selected_tag
awful.client.movetotag(current_tag, self:display()) if current_tag and self.last_tag ~= current_tag and self.visible then
else self:display():move_to_tag(current_tag)
self.visible = not self.visible else
self:display() self.visible = not self.visible
end self:display()
end
end end
setmetatable(quake, { __call = function(_, ...) return quake:new(...) end }) return setmetatable(quake, { __call = function(_, ...) return quake:new(...) end })
return quake

View File

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

View File

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

View File

@ -7,21 +7,18 @@
--]] --]]
local newtimer = require("lain.helpers").newtimer local helpers = require("lain.helpers")
local read_pipe = require("lain.helpers").read_pipe local shell = require("awful.util").shell
local wibox = require("wibox") local wibox = require("wibox")
local string = { match = string.match, local string = { match = string.match,
format = string.format } format = string.format }
local setmetatable = setmetatable local setmetatable = setmetatable
-- ALSA volume -- ALSA volume
-- lain.widgets.alsa -- lain.widgets.alsa
local alsa = { last_level = "0", last_status = "" }
local function worker(args) local function worker(args)
local alsa = { widget = wibox.widget.textbox() }
local args = args or {} local args = args or {}
local timeout = args.timeout or 5 local timeout = args.timeout or 5
local settings = args.settings or function() end local settings = args.settings or function() end
@ -29,31 +26,31 @@ local function worker(args)
alsa.cmd = args.cmd or "amixer" alsa.cmd = args.cmd or "amixer"
alsa.channel = args.channel or "Master" alsa.channel = args.channel or "Master"
alsa.togglechannel = args.togglechannel alsa.togglechannel = args.togglechannel
alsa.widget = wibox.widget.textbox('')
function alsa.update() local format_cmd = string.format("%s get %s", alsa.cmd, alsa.channel)
mixer = read_pipe(string.format("%s get %s", alsa.cmd, alsa.channel))
l,s = string.match(mixer, "([%d]+)%%.*%[([%l]*)")
-- HDMIs can have a channel different from Master for toggling mute if alsa.togglechannel then
if alsa.togglechannel then format_cmd = { shell, "-c", string.format("%s get %s; %s get %s",
s = string.match(read_pipe(string.format("%s get %s", alsa.cmd, alsa.togglechannel)), "%[(%a+)%]") alsa.cmd, alsa.channel, alsa.cmd, alsa.togglechannel) }
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
end end
timer_id = string.format("alsa-%s-%s", alsa.cmd, alsa.channel) alsa.last = {}
newtimer(timer_id, timeout, alsa.update)
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 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 helpers = require("lain.helpers")
local read_pipe = require("lain.helpers").read_pipe
local awful = require("awful") local awful = require("awful")
local beautiful = require("beautiful")
local naughty = require("naughty") local naughty = require("naughty")
local wibox = require("wibox")
local math = { modf = math.modf } local math = { modf = math.modf }
local mouse = mouse
local string = { format = string.format, local string = { format = string.format,
match = string.match, match = string.match,
rep = string.rep } rep = string.rep }
local type = type
local tonumber = tonumber local tonumber = tonumber
local setmetatable = setmetatable local setmetatable = setmetatable
-- ALSA volume bar -- ALSA volume bar
-- lain.widgets.alsabar -- lain.widgets.alsabar
local alsabar = { local alsabar = {
channel = "Master",
step = "1%",
colors = { colors = {
background = beautiful.bg_normal, background = "#000000",
mute = "#EB8F8F", mute = "#EB8F8F",
unmute = "#A4CE8A" 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, _current_level = 0,
_muted = false _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 function worker(args)
local args = args or {} local args = args or {}
local timeout = args.timeout or 5 local timeout = args.timeout or 5
local settings = args.settings or function() end local settings = args.settings or function() end
local width = args.width or 63 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 = args.ticks or false
local ticks_size = args.ticks_size or 7 local ticks_size = args.ticks_size or 7
local vertical = args.vertical or false local vertical = args.vertical or false
alsabar.cmd = args.cmd or "amixer" alsabar.cmd = args.cmd or "amixer"
alsabar.channel = args.channel or alsabar.channel alsabar.channel = args.channel or "Master"
alsabar.togglechannel = args.togglechannel alsabar.togglechannel = args.togglechannel
alsabar.step = args.step or alsabar.step alsabar.colors = args.colors or alsabar.colors
alsabar.colors = args.colors or alsabar.colors alsabar.followtag = args.followtag or false
alsabar.notifications = args.notifications or alsabar.notifications alsabar.notification_preset = args.notification_preset
alsabar.followmouse = args.followmouse or false
alsabar.bar = awful.widget.progressbar() if not alsabar.notification_preset then
alsabar.notification_preset = {}
alsabar.bar:set_background_color(alsabar.colors.background) alsabar.notification_preset.font = "Monospace 10"
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
end end
alsabar.bar:buttons(awful.util.table.join ( local format_cmd = string.format("%s get %s", alsabar.cmd, alsabar.channel)
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)
))
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 return alsabar
end 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 first_line = require("lain.helpers").first_line
local newtimer = require("lain.helpers").newtimer
local naughty = require("naughty") local naughty = require("naughty")
local wibox = require("wibox") local wibox = require("wibox")
local math = { abs = math.abs, local math = { abs = math.abs,
floor = math.floor, floor = math.floor,
log10 = math.log10, log10 = math.log10,
min = math.min } min = math.min }
local string = { format = string.format } local string = { format = string.format }
local ipairs = ipairs
local type = type local type = type
local tonumber = tonumber local tonumber = tonumber
local setmetatable = setmetatable local setmetatable = setmetatable
@ -27,7 +25,7 @@ local setmetatable = setmetatable
-- lain.widgets.bat -- lain.widgets.bat
local function worker(args) local function worker(args)
local bat = {} local bat = { widget = wibox.widget.textbox() }
local args = args or {} local args = args or {}
local timeout = args.timeout or 30 local timeout = args.timeout or 30
local batteries = args.batteries or (args.battery and {args.battery}) or {"BAT0"} 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 notify = args.notify or "on"
local settings = args.settings or function() end local settings = args.settings or function() end
bat.widget = wibox.widget.textbox('')
bat_notification_low_preset = { bat_notification_low_preset = {
title = "Battery low", title = "Battery low",
text = "Plug the cable!", text = "Plug the cable!",
@ -110,35 +106,48 @@ local function worker(args)
end end
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] 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" 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 ~= "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 -- 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 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 if bat_now.status == "Charging" then
rate_time = (sum_energy_full - sum_energy_now) / div rate_time = (sum_energy_full - sum_energy_now) / div
else -- Discharging else -- Discharging
rate_time = sum_energy_now / div rate_time = sum_energy_now / div
end end
if 0 < rate_time and rate_time < 0.01 then -- check for magnitude discrepancies (#199) 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_magnitude = math.abs(math.floor(math.log10(rate_time)))
rate_time = rate_time * 10^(rate_time_magnitude - 2) rate_time = rate_time * 10^(rate_time_magnitude - 2)
end end
end
local hours = math.floor(rate_time) local hours = math.floor(rate_time)
local minutes = math.floor((rate_time - hours) * 60) 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.perc = math.floor(math.min(100, (sum_energy_now / sum_energy_full) * 100))
bat_now.time = string.format("%02d:%02d", hours, minutes) bat_now.time = string.format("%02d:%02d", hours, minutes)
bat_now.watt = tonumber(string.format("%.2f", sum_rate_energy / 1e6)) 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 elseif bat_now.status == "Full" then
bat_now.perc = 100 bat_now.perc = 100
bat_now.time = "00:00" bat_now.time = "00:00"
@ -165,9 +174,9 @@ local function worker(args)
end end
end end
newtimer(battery, timeout, bat.update) newtimer("batteries", timeout, bat.update)
return setmetatable(bat, { __index = bat.widget }) return bat
end end
return setmetatable({}, { __call = function(_, ...) return worker(...) 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 awful = require("awful")
local beautiful = require("beautiful")
local naughty = require("naughty") local naughty = require("naughty")
local os = { date = os.date }
local io = { popen = io.popen }
local os = { date = os.date }
local mouse = mouse
local string = { format = string.format, local string = { format = string.format,
sub = string.sub,
gsub = string.gsub } gsub = string.gsub }
local ipairs = ipairs
local tonumber = tonumber local tonumber = tonumber
local setmetatable = setmetatable local setmetatable = setmetatable
-- Calendar notification -- Calendar notification
-- lain.widgets.calendar -- lain.widgets.calendar
local calendar = {} local calendar = { offset = 0 }
local cal_notification = nil
function calendar.hide() function calendar.hide()
if cal_notification ~= nil then if not calendar.notification then return end
naughty.destroy(cal_notification) naughty.destroy(calendar.notification)
cal_notification = nil calendar.notification = nil
end
end end
function calendar.show(t_out, inc_offset, scr) function calendar.show(t_out, inc_offset, scr)
calendar.hide() local today = os.date("%e")
local offs = inc_offset or 0
local f, c_text local f
local offs = inc_offset or 0
local tims = t_out or 0
local today = tonumber(os.date('%d'))
calendar.offset = calendar.offset + offs calendar.offset = calendar.offset + offs
if offs == 0 or calendar.offset == 0 local current_month = (offs == 0 or calendar.offset == 0)
then -- current month showing, today highlighted
calendar.offset = 0
calendar.notify_icon = calendar.icons .. today .. ".png"
-- bg and fg inverted to highlight today if current_month then -- today highlighted
f = io.popen(calendar.cal_format(today)) 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 else -- no current month showing, no day to highlight
local month = tonumber(os.date('%m')) local month = tonumber(os.date("%m"))
local year = tonumber(os.date('%Y')) local year = tonumber(os.date("%Y"))
month = month + calendar.offset month = month + calendar.offset
@ -68,55 +57,30 @@ function calendar.show(t_out, inc_offset, scr)
end end
calendar.notify_icon = nil 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 end
c_text = "<tt><span font='" .. calendar.font .. " " if calendar.followtag then
.. calendar.font_size .. "'><b>" calendar.notification_preset.screen = awful.screen.focused()
.. f:read() .. "</b>\n\n"
.. f:read() .. "\n"
.. f:read("*all"):gsub("\n*$", "")
.. "</span></tt>"
f:close()
if calendar.followmouse then
scrp = mouse.screen
else else
scrp = scr or calendar.scr_pos calendar.notification_preset.screen = src or 1
end end
cal_notification = naughty.notify({ helpers.async(f, function(ws)
text = c_text, fg, bg = calendar.notification_preset.fg, calendar.notification_preset.bg
icon = calendar.notify_icon, ws = ws:gsub("%c%[%d+[m]?%s?%d+%c%[%d+[m]?", markup.bold(markup.color(bg, fg, today)))
position = calendar.position, calendar.hide()
fg = calendar.fg, calendar.notification = naughty.notify({
bg = calendar.bg, preset = calendar.notification_preset,
timeout = tims, text = ws:gsub("\n*$", ""),
screen = scrp icon = calendar.notify_icon,
}) timeout = t_out or calendar.notification_preset.timeout or 5
})
end)
end end
function calendar.attach(widget, args) function calendar.attach(widget)
local args = args or {} widget:connect_signal("mouse::enter", function () calendar.show(0) end)
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)
widget:connect_signal("mouse::leave", function () calendar.hide() end) widget:connect_signal("mouse::leave", function () calendar.hide() end)
widget:buttons(awful.util.table.join(awful.button({ }, 1, function () widget:buttons(awful.util.table.join(awful.button({ }, 1, function ()
calendar.show(0, -1, calendar.scr_pos) end), 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))) calendar.show(0, 1, calendar.scr_pos) end)))
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 helpers = require("lain.helpers")
local json = require("lain.util.dkjson") local json = require("lain.util.dkjson")
local pread = require("awful.util").pread local focused = require("awful.screen").focused
local naughty = require("naughty") local pread = require("awful.util").pread
local wibox = require("wibox") local naughty = require("naughty")
local mouse = mouse local wibox = require("wibox")
local os = { getenv = os.getenv } local next = next
local os = { getenv = os.getenv }
local table = table
local setmetatable = setmetatable local setmetatable = setmetatable
-- Google Play Music Desktop infos -- Google Play Music Desktop infos
@ -24,12 +25,12 @@ local function worker(args)
local args = args or {} local args = args or {}
local timeout = args.timeout or 2 local timeout = args.timeout or 2
local notify = args.notify or "off" 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 local file_location = args.file_location or
os.getenv("HOME") .. "/.config/Google Play Music Desktop Player/json_store/playback.json" os.getenv("HOME") .. "/.config/Google Play Music Desktop Player/json_store/playback.json"
local settings = args.settings or function() end local settings = args.settings or function() end
gpmdp.widget = wibox.widget.textbox('') gpmdp.widget = wibox.widget.textbox()
gpmdp_notification_preset = { gpmdp_notification_preset = {
title = "Now playing", title = "Now playing",
@ -39,14 +40,13 @@ local function worker(args)
helpers.set_map("gpmdp_current", nil) helpers.set_map("gpmdp_current", nil)
function gpmdp.update() function gpmdp.update()
file, err = io.open(file_location, "r") local filelines = helpers.lines_from(file_location)
if not file
then if not next(filelines) then
gpm_now = { running = false, playing = false } local gpm_now = { running = false, playing = false }
else else
dict, pos, err = json.decode(file:read "*a", 1, nil) dict, pos, err = json.decode(table.concat(filelines), 1, nil)
file:close() local gpm_now = {}
gpm_now = {}
gpm_now.artist = dict.song.artist gpm_now.artist = dict.song.artist
gpm_now.album = dict.song.album gpm_now.album = dict.song.album
gpm_now.title = dict.song.title gpm_now.title = dict.song.title
@ -54,7 +54,7 @@ local function worker(args)
gpm_now.playing = dict.playing gpm_now.playing = dict.playing
end end
if (pread("pidof 'Google Play Music Desktop Player'") ~= '') then if pread("pidof 'Google Play Music Desktop Player'") ~= '' then
gpm_now.running = true gpm_now.running = true
else else
gpm_now.running = false gpm_now.running = false
@ -64,32 +64,29 @@ local function worker(args)
widget = gpmdp.widget widget = gpmdp.widget
settings() settings()
if gpm_now.playing if gpm_now.playing then
then if notify == "on" and gpm_now.title ~= helpers.get_map("gpmdp_current") then
if notify == "on" and gpm_now.title ~= helpers.get_map("gpmdp_current")
then
helpers.set_map("gpmdp_current", gpm_now.title) helpers.set_map("gpmdp_current", gpm_now.title)
os.execute("curl " .. gpm_now.cover_url .. " -o /tmp/gpmcover.png")
if followmouse then if followtag then gpmdp_notification_preset.screen = focused() end
gpmdp_notification_preset.screen = mouse.screen
end
gpmdp.id = naughty.notify({ helpers.async(string.format("curl %d -o /tmp/gpmcover.png", gpm_now.cover_url),
preset = gpmdp_notification_preset, function(f)
icon = "/tmp/gpmcover.png", gpmdp.id = naughty.notify({
replaces_id = gpmdp.id, preset = gpmdp_notification_preset,
}).id icon = "/tmp/gpmcover.png",
replaces_id = gpmdp.id
}).id
end)
end end
elseif not gpm_now.running elseif not gpm_now.running then
then
helpers.set_map("gpmdp_current", nil) helpers.set_map("gpmdp_current", nil)
end end
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 end
return setmetatable(gpmdp, { __call = function(_, ...) return worker(...) end }) return setmetatable(gpmdp, { __call = function(_, ...) return worker(...) end })

View File

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

View File

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

View File

@ -6,73 +6,49 @@
--]] --]]
local awful = require("awful") local async = require("lain.helpers").async
local os = os local awful = require("awful")
local spawn = awful.util.spawn_with_shell local execute = os.execute
local type = type
local setmetatable = setmetatable
-- Redshift -- Redshift
-- lain.widgets.contrib.redshift -- lain.widgets.contrib.redshift
local redshift = {} local redshift = { active = false, pid = nil }
local attached = false -- true if attached to a widget function redshift:start()
local active = false -- true if redshift is active execute("pkill redshift")
local running = false -- true if redshift was initialized awful.spawn.with_shell("redshift -x") -- clear adjustments
local update_fnct = function() end -- Function that is run each time redshift is toggled. See redshift:attach(). redshift.pid = awful.spawn.with_shell("redshift")
redshift.active = true
local function init() if type(redshift.update_fun) == "function" then
-- As there is no way to determine if redshift was previously redshift.update_fun(redshift.active)
-- toggled off (i.e Awesome on-the-fly restart), kill redshift to make sure end
os.execute("pkill redshift")
-- Remove existing color adjustment
spawn("redshift -x")
-- (Re)start redshift
spawn("redshift")
running = true
active = true
end end
function redshift:toggle() function redshift:toggle()
if running then async({ awful.util.shell, "-c", string.format("ps -p %d -o pid=", redshift.pid) }, function(f)
-- Sending -USR1 toggles redshift (See project website) if f and #f > 0 then -- redshift is running
os.execute("pkill -USR1 redshift") -- Sending -USR1 toggles redshift (See project website)
active = not active execute("pkill -USR1 redshift")
else redshift.active = not redshift.active
init() else -- not started or killed, (re)start it
end redshift:start()
update_fnct() end
end redshift.update_fun(redshift.active)
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
end end
-- Attach to a widget -- Attach to a widget
-- Provides a button which toggles redshift on/off on click -- Provides a button which toggles redshift on/off on click
-- @param widget: Widget to attach to. -- @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. -- Use it to update widget text or icons on status change.
function redshift:attach(widget, fnct) function redshift:attach(widget, fun)
update_fnct = fnct or function() end redshift.update_fun = fun or function() end
if not attached then if not redshift.pid then redshift:start() end
init() if widget then
attached = true widget:buttons(awful.util.table.join(awful.button({}, 1, function () redshift:toggle() end)))
update_fnct()
end end
widget:buttons(awful.util.table.join( awful.button({}, 1, function () redshift:toggle() end) ))
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 helpers = require("lain.helpers")
local markup = require("lain.util").markup
local awful = require("awful") local awful = require("awful")
local beautiful = require("beautiful") local naughty = require("naughty")
local naughty = require("naughty") local string = { format = string.format, gsub = string.gsub }
local mouse = mouse
local io = io
local string = { len = string.len }
local tonumber = tonumber
local setmetatable = setmetatable
-- Taskwarrior notification -- Taskwarrior notification
-- lain.widgets.contrib.task -- lain.widgets.contrib.task
local 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() function task.hide()
if task_notification ~= nil then if not task.notification then return end
naughty.destroy(task_notification) naughty.destroy(task.notification)
task_notification = nil task.notification = nil
end
end end
function task.show(scr_pos) function task.show(scr)
task.hide() task.hide()
local f, c_text, scrp if task.followtag then
task.notification_preset.screen = awful.screen.focused()
if task.followmouse then elseif scr then
scrp = mouse.screen task.notification_preset.screen = scr
else
scrp = scr_pos or task.scr_pos
end end
f = io.popen('task ' .. task.cmdline) helpers.async(task.show_cmd, function(f)
c_text = "<span font='" task.notification = naughty.notify({
.. task.font .. " " preset = task.notification_preset,
.. task.font_size .. "'>" title = task.show_cmd,
.. awful.util.escape(f:read("*all"):gsub("\n*$", "")) text = markup.font(task.notification_preset.font,
.. "</span>" awful.util.escape(f:gsub("\n*$", "")))
f:close() })
end)
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
})
end end
function task.prompt_add() function task.prompt()
awful.prompt.run({ prompt = "Add task: " }, awful.prompt.run {
mypromptbox[mouse.screen].widget, prompt = task.prompt_text,
function (...) textbox = awful.screen.focused().mypromptbox.widget,
local f = io.popen("task add " .. ...) exe_callback = function(t)
c_text = "\n<span font='" helpers.async(t, function(f)
.. task.font .. " " naughty.notify {
.. task.font_size .. "'>" preset = task.notification_preset,
.. awful.util.escape(f:read("*all")) title = t,
.. "</span>" text = markup.font(task.notification_preset.font,
f:close() awful.util.escape(f:gsub("\n*$", "")))
}
naughty.notify({ end)
text = c_text, end,
icon = task.notify_icon, history_path = awful.util.getdir("cache") .. "/history_task"
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")
end end
function task.attach(widget, args) 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 if not task.notification_preset then
task.font = args.font or beautiful.font:sub(beautiful.font:find(""), task.notification_preset = {
findLast(beautiful.font, " ")) font = "Monospace 10",
task.fg = args.fg or beautiful.fg_normal or "#FFFFFF" icon = helpers.icons_dir .. "/taskwarrior.png"
task.bg = args.bg or beautiful.bg_normal or "#FFFFFF" }
task.position = args.position or "top_right" end
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"
task.notify_icon = icons_dir .. "/taskwarrior/task.png" if widget then
task.notify_icon_small = icons_dir .. "/taskwarrior/tasksmall.png" widget:connect_signal("mouse::enter", function () task.show() end)
widget:connect_signal("mouse::leave", function () task.hide() end)
widget:connect_signal("mouse::enter", function () task.show(task.scr_pos) end) 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 debug = { getinfo = debug.getinfo }
local newtimer = require("lain.helpers").newtimer local newtimer = require("lain.helpers").newtimer
local first_line = require("lain.helpers").first_line local first_line = require("lain.helpers").first_line
local beautiful = require("beautiful")
local naughty = require("naughty") local naughty = require("naughty")
local wibox = require("wibox") local wibox = require("wibox")
local string = { format = string.format } local string = { format = string.format }
local math = { floor = math.floor } local math = { floor = math.floor }
local tostring = tostring local tostring = tostring
local setmetatable = setmetatable local setmetatable = setmetatable
package.path = debug.getinfo(1,"S").source:match[[^@?(.*[\/])[^\/]-$]] .. "?.lua;" .. package.path package.path = debug.getinfo(1,"S").source:match[[^@?(.*[\/])[^\/]-$]] .. "?.lua;" .. package.path
local smapi = require("smapi") local smapi = require("smapi")
-- ThinkPad SMAPI-enabled battery info widget -- ThinkPad SMAPI-enabled battery info widget
-- lain.widgets.contrib.tpbat -- lain.widgets.contrib.tpbat
local tpbat = { } local tpbat = {}
local tpbat_notification = nil
function tpbat:hide() function tpbat.hide()
if tpbat_notification ~= nil if not tpbat.notification then return end
then naughty.destroy(tpbat.notification)
naughty.destroy(tpbat_notification) tpbat.notification = nil
tpbat_notification = nil
end
end end
function tpbat:show(t_out) function tpbat.show(t_out)
tpbat:hide() tpbat.hide()
local bat = self.bat local bat = tpbat.bat
local t_out = t_out or 0
if bat == nil or not bat:installed() then return end 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 mfgr = bat:get('manufacturer') or "no_mfgr"
local model = bat:get('model') or "no_model" local model = bat:get('model') or "no_model"
local chem = bat:get('chemistry') or "no_chem" local chem = bat:get('chemistry') or "no_chem"
@ -57,10 +51,8 @@ function tpbat:show(t_out)
local time = bat:remaining_time() local time = bat:remaining_time()
local msg = "\t" local msg = "\t"
if status ~= "idle" and status ~= "nil" if status ~= "idle" and status ~= "nil" then
then if time == "N/A" then
if time == "N/A"
then
msg = "...Calculating time remaining..." msg = "...Calculating time remaining..."
else else
msg = time .. (status == "charging" and " until charged" or " remaining") 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) 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) .. string.format("\n%s \t\t\t %s", status:upper(), msg)
tpbat_notification = naughty.notify({ tpbat.notification = naughty.notify({
preset = { fg = beautiful.fg_normal }, text = str,
text = str,
timeout = t_out, timeout = t_out,
screen = client.focus and client.focus.screen or 1 screen = client.focus and client.focus.screen or 1
}) })
end end
@ -89,7 +80,7 @@ function tpbat.register(args)
tpbat.bat = smapi:battery(battery) -- Create a new battery tpbat.bat = smapi:battery(battery) -- Create a new battery
local bat = tpbat.bat local bat = tpbat.bat
tpbat.widget = wibox.widget.textbox('') tpbat.widget = wibox.widget.textbox()
bat_notification_low_preset = { bat_notification_low_preset = {
title = "Battery low", title = "Battery low",
@ -117,7 +108,7 @@ function tpbat.register(args)
}) })
end end
function update() function tpbat.update()
bat_now = { bat_now = {
status = "Not present", status = "Not present",
perc = "N/A", perc = "N/A",
@ -156,15 +147,16 @@ function tpbat.register(args)
end end
widget = tpbat.widget widget = tpbat.widget
settings() settings()
end 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::enter', function () tpbat.show() end)
widget:connect_signal('mouse::leave', function () tpbat:hide() end) widget:connect_signal('mouse::leave', function () tpbat.hide() end)
return tpbat.widget return tpbat
end end
return setmetatable(tpbat, { __call = function(_, ...) return tpbat.register(...) 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 time_val = bat_now.status == 'discharging' and 'remaining_running_time' or 'remaining_charging_time'
local mins_left = self:get(time_val) local mins_left = self:get(time_val)
if mins_left:find("^%d+") == nil if not mins_left:find("^%d+") then return "N/A" end
then
return "N/A"
end
local hrs = math.floor(mins_left / 60) local hrs = math.floor(mins_left / 60)
local min = mins_left % 60 local min = mins_left % 60
return string.format("%02d:%02d", hrs, min) return string.format("%02d:%02d", hrs, min)
end end

View File

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

View File

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

View File

@ -7,77 +7,71 @@
--]] --]]
local helpers = require("lain.helpers") local helpers = require("lain.helpers")
local async = require("lain.asyncshell")
local naughty = require("naughty") local naughty = require("naughty")
local wibox = require("wibox") local wibox = require("wibox")
local mouse = mouse
local string = { format = string.format, local string = { format = string.format,
gsub = string.gsub } gsub = string.gsub }
local type = type
local tonumber = tonumber local tonumber = tonumber
local setmetatable = setmetatable local setmetatable = setmetatable
-- Mail IMAP check -- Mail IMAP check
-- lain.widgets.imap -- lain.widgets.imap
local function worker(args) local function worker(args)
local imap = {} local imap = { widget = wibox.widget.textbox() }
local args = args or {} 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 head_command = "curl --connect-timeout 3 -fsm 3"
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 request = "-X 'SEARCH (UNSEEN)'" local request = "-X 'SEARCH (UNSEEN)'"
if not server or not mail or not password then return end
helpers.set_map(mail, 0) helpers.set_map(mail, 0)
if not is_plain then 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 end
imap.widget = wibox.widget.textbox('')
function update() function update()
mail_notification_preset = { mail_notification_preset = {
icon = helpers.icons_dir .. "mail.png", icon = helpers.icons_dir .. "mail.png",
position = "top_left" position = "top_left"
} }
if followmouse then if followtag then
mail_notification_preset.screen = mouse.screen mail_notification_preset.screen = awful.screen.focused()
end end
curl = string.format("%s --url imaps://%s:%s/INBOX -u %s:%q %s -k", curl = string.format("%s --url imaps://%s:%s/INBOX -u %s:%q %s -k",
head_command, server, port, mail, password, request) head_command, server, port, mail, password, request)
async.request(curl, function(f) helpers.async(curl, function(f)
_, mailcount = string.gsub(f, "%d+", "") _, mailcount = string.gsub(f, "%d+", "")
_ = nil _ = nil
widget = imap.widget widget = imap.widget
settings() settings()
if mailcount >= 1 and mailcount > helpers.get_map(mail) if mailcount >= 1 and mailcount > helpers.get_map(mail) then
then
if mailcount == 1 then if mailcount == 1 then
nt = mail .. " has one new message" nt = mail .. " has one new message"
else else
nt = mail .. " has <b>" .. mailcount .. "</b> new messages" nt = mail .. " has <b>" .. mailcount .. "</b> new messages"
end end
naughty.notify({ naughty.notify({ preset = mail_notification_preset, text = nt })
preset = mail_notification_preset,
text = nt
})
end end
helpers.set_map(mail, mailcount) helpers.set_map(mail, mailcount)
@ -85,9 +79,9 @@ local function worker(args)
end 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 end
return setmetatable({}, { __call = function(_, ...) return worker(...) 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 wibox = require("wibox")
local gmatch = string.gmatch
local io = { lines = io.lines } local lines = io.lines
local math = { floor = math.floor } local floor = math.floor
local string = { gmatch = string.gmatch }
local setmetatable = setmetatable local setmetatable = setmetatable
-- Memory usage (ignoring caches) -- Memory usage (ignoring caches)
@ -26,25 +23,24 @@ local function worker(args)
local timeout = args.timeout or 2 local timeout = args.timeout or 2
local settings = args.settings or function() end local settings = args.settings or function() end
mem.widget = wibox.widget.textbox('') mem.widget = wibox.widget.textbox()
function update() function mem.update()
mem_now = {} mem_now = {}
for line in io.lines("/proc/meminfo") for line in lines("/proc/meminfo") do
do for k, v in gmatch(line, "([%a]+):[%s]+([%d]+).+") do
for k, v in string.gmatch(line, "([%a]+):[%s]+([%d]+).+") if k == "MemTotal" then mem_now.total = floor(v / 1024 + 0.5)
do elseif k == "MemFree" then mem_now.free = floor(v / 1024 + 0.5)
if k == "MemTotal" then mem_now.total = math.floor(v / 1024) elseif k == "Buffers" then mem_now.buf = floor(v / 1024 + 0.5)
elseif k == "MemFree" then mem_now.free = math.floor(v / 1024) elseif k == "Cached" then mem_now.cache = floor(v / 1024 + 0.5)
elseif k == "Buffers" then mem_now.buf = math.floor(v / 1024) elseif k == "SwapTotal" then mem_now.swap = floor(v / 1024 + 0.5)
elseif k == "Cached" then mem_now.cache = math.floor(v / 1024) elseif k == "SwapFree" then mem_now.swapf = floor(v / 1024 + 0.5)
elseif k == "SwapTotal" then mem_now.swap = math.floor(v / 1024) elseif k == "SReclaimable" then mem_now.srec = floor(v / 1024 + 0.5)
elseif k == "SwapFree" then mem_now.swapf = math.floor(v / 1024)
end end
end 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.swapused = mem_now.swap - mem_now.swapf
mem_now.perc = math.floor(mem_now.used / mem_now.total * 100) mem_now.perc = math.floor(mem_now.used / mem_now.total * 100)
@ -52,9 +48,9 @@ local function worker(args)
settings() settings()
end end
newtimer("mem", timeout, update) helpers.newtimer("mem", timeout, mem.update)
return mem.widget return mem
end end
return setmetatable(mem, { __call = function(_, ...) return worker(...) end }) return setmetatable(mem, { __call = function(_, ...) return worker(...) end })

View File

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

View File

@ -10,55 +10,40 @@
local helpers = require("lain.helpers") local helpers = require("lain.helpers")
local naughty = require("naughty") local naughty = require("naughty")
local wibox = require("wibox") local wibox = require("wibox")
local string = { format = string.format, local string = { format = string.format,
gsub = string.gsub,
match = string.match } match = string.match }
local setmetatable = setmetatable local setmetatable = setmetatable
-- Network infos -- Network infos
-- lain.widgets.net -- lain.widgets.net
local function worker(args) 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 args = args or {}
local ws = helpers.read_pipe("ip link show | cut -d' ' -f2,9") local timeout = args.timeout or 2
ws = ws:match("%w+: UP") or ws:match("ppp%w+: UNKNOWN") local units = args.units or 1024 --kb
if ws then return { ws:match("(%w+):") } local notify = args.notify or "on"
else return {} end local screen = args.screen or 1
end local settings = args.settings or function() 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('')
-- Compatibility with old API where iface was a string corresponding to 1 interface -- Compatibility with old API where iface was a string corresponding to 1 interface
if type(iface) == "string" then net.iface = (args.iface and (type(args.iface) == "string" and {args.iface}) or
iftable = {iface} (type(args.iface) == "table" and args.iface)) or {}
else
iftable = iface 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 end
-- Mark all devices as initially online/active if #net.iface == 0 then net.get_device() end
for i, dev in ipairs(iftable) do
helpers.set_map(dev, true)
end
function update() 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 -- These are the totals over all specified interfaces
net_now = { net_now = {
-- New api - Current state of requested devices -- New api - Current state of requested devices
@ -72,15 +57,14 @@ local function worker(args)
local total_t = 0 local total_t = 0
local total_r = 0 local total_r = 0
for i, dev in ipairs(iftable) do for i, dev in ipairs(net.iface) do
local dev_now = {} local dev_now = {}
local dev_before = net.devices[dev] or { last_t = 0, last_r = 0 } 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.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.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.sent = (now_t - dev_before.last_t) / timeout / units dev_now.sent = (now_t - dev_before.last_t) / timeout / units
dev_now.received = (now_r - dev_before.last_r) / 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.sent = net_now.sent + dev_now.sent
net_now.received = net_now.received + dev_now.received net_now.received = net_now.received + dev_now.received
dev_now.sent = string.gsub(string.format('%.1f', dev_now.sent), ',', '.') dev_now.sent = string.format('%.1f', dev_now.sent)
dev_now.received = string.gsub(string.format('%.1f', dev_now.received), ',', '.') dev_now.received = string.format('%.1f', dev_now.received)
dev_now.last_t = now_t dev_now.last_t = now_t
dev_now.last_r = now_r dev_now.last_r = now_r
@ -113,32 +97,27 @@ local function worker(args)
helpers.set_map(dev, true) helpers.set_map(dev, true)
end end
-- Old api compatibility
net_now.carrier = dev_now.carrier net_now.carrier = dev_now.carrier
net_now.state = dev_now.state net_now.state = dev_now.state
-- And new api
net_now.devices[dev] = dev_now 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 -- totals across all specified devices
end end
if total_t ~= net.last_t or total_r ~= net.last_r then 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.format('%.1f', net_now.sent)
net_now.sent = string.gsub(string.format('%.1f', net_now.sent), ',', '.') net_now.received = string.format('%.1f', net_now.received)
net_now.received = string.gsub(string.format('%.1f', net_now.received), ',', '.') net.last_t = total_t
net.last_r = total_r
net.last_t = total_t
net.last_r = total_r
end end
widget = net.widget widget = net.widget
settings() settings()
end end
helpers.newtimer(iface, timeout, update) helpers.newtimer("network", timeout, update)
return setmetatable(net, { __index = net.widget }) return net
end end
return setmetatable({}, { __call = function(_, ...) return worker(...) end }) return setmetatable({}, { __call = function(_, ...) return worker(...) end })

View File

@ -6,55 +6,57 @@
--]] --]]
local read_pipe = require("lain.helpers").read_pipe local helpers = require("lain.helpers")
local newtimer = require("lain.helpers").newtimer local shell = require("awful.util").shell
local wibox = require("wibox") local wibox = require("wibox")
local string = { gmatch = string.gmatch, local string = { gmatch = string.gmatch,
match = string.match, match = string.match,
format = string.format } format = string.format }
local setmetatable = setmetatable local setmetatable = setmetatable
-- PulseAudio volume -- PulseAudio volume
-- lain.widgets.pulseaudio -- lain.widgets.pulseaudio
local pulseaudio = {}
local function worker(args) local function worker(args)
local args = args or {} local pulseaudio = { wibox.widget.textbox() }
local timeout = args.timeout or 5 local args = args or {}
local settings = args.settings or function() end local devicetype = args.devicetype or "sink"
local scallback = args.scallback 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.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'"
pulseaudio.widget = wibox.widget.textbox('')
function pulseaudio.update() function pulseaudio.update()
if scallback then pulseaudio.cmd = scallback() end if scallback then pulseaudio.cmd = scallback() end
local s = read_pipe(pulseaudio.cmd)
volume_now = {} helpers.async({ shell, "-c", pulseaudio.cmd }, function(s)
volume_now.index = string.match(s, "index: (%S+)") or "N/A" volume_now = {
volume_now.sink = string.match(s, "device.string = \"(%S+)\"") or "N/A" index = string.match(s, "index: (%S+)") or "N/A",
volume_now.muted = string.match(s, "muted: (%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 local ch = 1
volume_now.channel = {} volume_now.channel = {}
for v in string.gmatch(s, ":.-(%d+)%%") do for v in string.gmatch(s, ":.-(%d+)%%") do
volume_now.channel[ch] = v volume_now.channel[ch] = v
ch = ch + 1 ch = ch + 1
end end
volume_now.left = volume_now.channel[1] or "N/A" volume_now.left = volume_now.channel[1] or "N/A"
volume_now.right = volume_now.channel[2] or "N/A" volume_now.right = volume_now.channel[2] or "N/A"
widget = pulseaudio.widget widget = pulseaudio.widget
settings()
end
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 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 helpers = require("lain.helpers")
local read_pipe = require("lain.helpers").read_pipe
local awful = require("awful") local awful = require("awful")
local beautiful = require("beautiful")
local naughty = require("naughty") local naughty = require("naughty")
local wibox = require("wibox")
local math = { modf = math.modf } local math = { modf = math.modf }
local mouse = mouse
local string = { format = string.format, local string = { format = string.format,
gmatch = string.gmatch,
match = string.match, match = string.match,
rep = string.rep } rep = string.rep }
local type = type
local tonumber = tonumber local tonumber = tonumber
local setmetatable = setmetatable local setmetatable = setmetatable
-- ALSA volume bar -- Pulseaudio volume bar
-- lain.widgets.pulsebar -- lain.widgets.pulsebar
local pulsebar = { local pulsebar = {
sink = 0,
step = "1%",
colors = { colors = {
background = beautiful.bg_normal, background = "#000000",
mute = "#EB8F8F", mute = "#EB8F8F",
unmute = "#A4CE8A" 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, _current_level = 0,
_muted = false _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 function worker(args)
local args = args or {} local args = args or {}
local timeout = args.timeout or 5 local timeout = args.timeout or 5
@ -102,79 +44,103 @@ local function worker(args)
local vertical = args.vertical or false local vertical = args.vertical or false
local scallback = args.scallback 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.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.colors = args.colors or pulsebar.colors
pulsebar.notifications = args.notifications or pulsebar.notifications
pulsebar.sink = args.sink or 0 pulsebar.sink = args.sink or 0
pulsebar.step = args.step or pulsebar.step pulsebar.colors = args.colors or pulsebar.colors
pulsebar.followmouse = args.followmouse or false pulsebar.followtag = args.followtag or false
pulsebar.notifications = args.notification_preset
pulsebar.bar = awful.widget.progressbar() if not pulsebar.notification_preset then
pulsebar.notification_preset = {}
pulsebar.bar:set_background_color(pulsebar.colors.background) pulsebar.notification_preset.font = "Monospace 10"
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
end end
pulsebar.bar:buttons(awful.util.table.join ( pulsebar.bar = wibox.widget {
awful.button({}, 1, function() forced_height = height,
awful.util.spawn(pulsebar.mixer) forced_width = width,
end), color = pulsebar.colors.unmute,
awful.button({}, 2, function() background_color = pulsebar.colors.background,
awful.util.spawn(string.format("pactl set-sink-volume %d 100%%", pulsebar.sink)) margins = 1,
pulsebar.update() paddings = 1,
end), ticks = ticks,
awful.button({}, 3, function() ticks_size = ticks_size,
awful.util.spawn(string.format("pactl set-sink-mute %d toggle", pulsebar.sink)) widget = wibox.widget.progressbar,
pulsebar.update() layout = vertical and wibox.container.rotate
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)
))
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 return pulsebar
end end

View File

@ -7,13 +7,10 @@
--]] --]]
local newtimer = require("lain.helpers").newtimer local helpers = require("lain.helpers")
local wibox = require("wibox") local wibox = require("wibox")
local io = { open = io.open } local io = { open = io.open }
local string = { match = string.match } local string = { match = string.match }
local setmetatable = setmetatable local setmetatable = setmetatable
-- System load -- System load
@ -21,13 +18,13 @@ local setmetatable = setmetatable
local sysload = {} local sysload = {}
local function worker(args) local function worker(args)
local args = args or {} local args = args or {}
local timeout = args.timeout or 2 local timeout = args.timeout or 2
local settings = args.settings or function() end 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 f = io.open("/proc/loadavg")
local ret = f:read("*all") local ret = f:read("*all")
f:close() f:close()
@ -38,8 +35,9 @@ local function worker(args)
settings() settings()
end end
newtimer("sysload", timeout, update) helpers.newtimer("sysload", timeout, sysload.update)
return sysload.widget
return sysload
end end
return setmetatable(sysload, { __call = function(_, ...) return worker(...) 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 wibox = require("wibox")
local io = { open = io.open } local io = { open = io.open }
local tonumber = tonumber local tonumber = tonumber
local setmetatable = setmetatable local setmetatable = setmetatable
-- coretemp -- coretemp
@ -25,9 +22,9 @@ local function worker(args)
local tempfile = args.tempfile or "/sys/class/thermal/thermal_zone0/temp" local tempfile = args.tempfile or "/sys/class/thermal/thermal_zone0/temp"
local settings = args.settings or function() end 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) local f = io.open(tempfile)
if f then if f then
coretemp_now = tonumber(f:read("*all")) / 1000 coretemp_now = tonumber(f:read("*all")) / 1000
@ -40,9 +37,9 @@ local function worker(args)
settings() settings()
end end
newtimer("coretemp", timeout, update) helpers.newtimer("coretemp", timeout, temp.update)
return temp.widget return temp
end end
return setmetatable(temp, { __call = function(_, ...) return worker(...) end }) return setmetatable(temp, { __call = function(_, ...) return worker(...) end })

View File

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

2
wiki

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