Merge pull request #1 from copycat-killer/master

Merge upstream
This commit is contained in:
trap000d 2016-12-08 14:38:29 +13:00 committed by GitHub
commit b8adf93221
29 changed files with 901 additions and 251 deletions

339
LICENSE Normal file
View File

@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
{description}
Copyright (C) {year} {fullname}
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
{signature of Ty Coon}, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

View File

@ -43,5 +43,5 @@ Screenshots
.. _GNU-GPL2: http://www.gnu.org/licenses/gpl-2.0.html
.. _awesome-vain: https://github.com/vain/awesome-vain
.. _Awesome: http://awesome.naquadah.org/
.. _Awesome: https://github.com/awesomeWM/awesome
.. _wiki: https://github.com/copycat-killer/lain/wiki

View File

@ -52,7 +52,7 @@ function asyncshell.request(command, callback, timeout)
id, formatted_command
)
if type(awful.spawn) == 'table' then
if type(awful.spawn) == 'table' and awful.spawn.with_shell then
awful.spawn.with_shell(req)
else
awful.util.spawn_with_shell(req)

View File

@ -8,6 +8,7 @@
local debug = require("debug")
local assert = assert
local capi = { timer = (type(timer) == 'table' and timer or require ("gears.timer")) }
local io = { open = io.open,
lines = io.lines,
@ -93,9 +94,9 @@ function helpers.newtimer(_name, timeout, fun, nostart)
local name = timeout
if not helpers.timer_table[name] then
helpers.timer_table[name] = capi.timer({ timeout = timeout })
helpers.timer_table[name]:start()
end
helpers.timer_table[name]:connect_signal("timeout", fun)
helpers.timer_table[name]:start()
if not nostart then
helpers.timer_table[name]:emit_signal("timeout")
end
@ -105,7 +106,7 @@ end
-- {{{ Pipe operations
-- read the full output of a pipe (command)
-- read the full output of a command output
function helpers.read_pipe(cmd)
local f = assert(io.popen(cmd))
local output = f:read("*all")
@ -113,6 +114,16 @@ function helpers.read_pipe(cmd)
return output
end
-- return line iterator of a command output
function helpers.pipelines(...)
local f = assert(io.popen(...))
return function () -- iterator
local data = f:read()
if data == nil then f:close() end
return data
end
end
-- }}}
-- {{{ A map utility
@ -148,4 +159,5 @@ function helpers.spairs(t)
end
--}}}
return helpers

28
lain-git.rockspec Normal file
View File

@ -0,0 +1,28 @@
package = "lain"
version = "git"
source = {
url = "https://github.com/copycat-killer/lain",
tag = "git"
}
description = {
summary = "Layout, widgets and utilities for Awesome WM",
detailed = [[
Successor of awesome-vain, this module provides new layouts, a set of widgets and utility functions, in order to improve Awesome usability and configurability.
Optional dependencies: alsa-utils (for alsamixer); curl; imagemagick.
]],
homepage = "https://github.com/copycat-killer/lain",
license = "GPL v2"
}
dependencies = {
"lua >= 5.1",
"awesome >= 3.5",
"alsa-utils",
"curl",
"imagemagick"
}
supported_platforms = { "linux" }
build = {
type = "builtin",
modules = { lain = "init.lua" }
}

View File

@ -11,10 +11,11 @@
# -------------------------------------------------------------------------
# Decoding options
# -------------------------------------------------------------------------
USAGE="Usage: $0 [-h(elp)] | [-n(arrow mode)] | [-w(eb output)]"
USAGE="Usage: $0 [-h(elp)] | [-n(arrow mode)] | [-w(eb output) | --type=<fstype> | --exclude-type=<fstype>]"
NARROW_MODE=0
WEB_OUTPUT=0
DF_OPTIONS=""
while [ $# -gt 0 ]; do
case "$1" in
@ -31,6 +32,12 @@ NARROW_MODE=1
"-w" )
WEB_OUTPUT=1
;;
--type=*)
DF_OPTIONS+=" $1"
;;
--exclude-type=*)
DF_OPTIONS+=" $1"
;;
* )
echo $USAGE
exit
@ -58,6 +65,9 @@ AWK_COMMAND="/usr/bin/env gawk"
;;
esac
# Add additional df options
DF_COMMAND+=$DF_OPTIONS
# -------------------------------------------------------------------------
# Grabbing "df" result
# -------------------------------------------------------------------------

View File

@ -98,7 +98,7 @@ function util.mc(c)
g.height = math.sqrt(mwfact) * mg.height
g.x = mg.x + (mg.width - g.width) / 2
g.y = mg.y + (mg.height - g.height) / 2
c:geometry(g)
if c then c:geometry(g) end -- if c is still a valid object
end
-- Read the nice value of pid from /proc.

162
util/quake.lua Normal file
View File

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

View File

@ -28,12 +28,18 @@ local function worker(args)
alsa.cmd = args.cmd or "amixer"
alsa.channel = args.channel or "Master"
alsa.togglechannel = args.togglechannel
alsa.widget = wibox.widget.textbox('')
function alsa.update()
mixer = read_pipe(string.format("%s get %s", alsa.cmd, alsa.channel))
l,s = string.match(mixer, "([%d]+)%%.*%[([%l]*)")
-- HDMIs can have a channel different from Master for toggling mute
if alsa.togglechannel then
s = string.match(read_pipe(string.format("%s get %s", alsa.cmd, alsa.togglechannel)), "%[(%a+)%]")
end
if alsa.last_level ~= l or alsa.last_status ~= s then
volume_now = { level = l, status = s }
alsa.last_level = l

View File

@ -27,7 +27,7 @@ local setmetatable = setmetatable
-- lain.widgets.alsabar
local alsabar = {
channel = "Master",
step = "2%",
step = "1%",
colors = {
background = beautiful.bg_normal,
@ -104,6 +104,7 @@ local function worker(args)
alsabar.cmd = args.cmd or "amixer"
alsabar.channel = args.channel or alsabar.channel
alsabar.togglechannel = args.togglechannel
alsabar.step = args.step or alsabar.step
alsabar.colors = args.colors or alsabar.colors
alsabar.notifications = args.notifications or alsabar.notifications
@ -127,9 +128,14 @@ local function worker(args)
-- 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)
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
@ -153,6 +159,10 @@ local function worker(args)
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()

View File

@ -26,8 +26,8 @@ local function worker(args)
base.widget = wibox.widget.textbox('')
function base.update()
if output ~= base.prev then
output = read_pipe(cmd)
if output ~= base.prev then
widget = base.widget
settings()
base.prev = output

View File

@ -13,10 +13,14 @@ local first_line = require("lain.helpers").first_line
local naughty = require("naughty")
local wibox = require("wibox")
local math = { floor = math.floor, min = math.min }
local math = { abs = math.abs,
floor = math.floor,
log10 = math.log10,
min = math.min }
local string = { format = string.format }
local tonumber = tonumber
local type = type
local tonumber = tonumber
local setmetatable = setmetatable
-- Battery infos
@ -50,7 +54,7 @@ local function worker(args)
}
bat_now = {
status = "Not present",
status = "N/A",
ac_status = "N/A",
perc = "N/A",
time = "N/A",
@ -58,24 +62,26 @@ local function worker(args)
}
bat_now.n_status = {}
bat_now.n_perc = {}
for i = 1, #batteries do
bat_now.n_status[i] = "Not present"
bat_now.n_status[i] = "N/A"
bat_now.n_perc[i] = 0
end
function update()
function bat.update()
local sum_rate_current = 0
local sum_rate_voltage = 0
local sum_rate_power = 0
local sum_rate_energy = 0
local sum_energy_now = 0
local sum_energy_full = 0
local sum_energy_percentage = 0
local pspath = "/sys/class/power_supply/"
for i, battery in ipairs(batteries) do
local bstr = "/sys/class/power_supply/" .. battery
local bstr = pspath .. battery
local present = first_line(bstr .. "/present")
if present == "1"
then
if tonumber(present) == 1 then
-- current_now(I)[uA], voltage_now(U)[uV], power_now(P)[uW]
local rate_current = tonumber(first_line(bstr .. "/current_now"))
local rate_voltage = tonumber(first_line(bstr .. "/voltage_now"))
@ -92,64 +98,74 @@ local function worker(args)
local energy_percentage = tonumber(first_line(bstr .. "/capacity")) or
math.floor((energy_now / energy_full) * 100)
if bat_now.n_status[i] ~= "Charging" and bat_now.n_status[i] ~= "Discharging"
then
bat_now.n_status[i] = first_line(bstr .. "/status") or "N/A"
end
bat_now.n_perc[i] = energy_percentage or bat_now.n_perc[i]
sum_rate_current = sum_rate_current + (rate_current or 0)
sum_rate_voltage = sum_rate_voltage + rate_voltage
sum_rate_power = sum_rate_power + (rate_power or ((rate_voltage * rate_current) / 1e6))
sum_energy_now = sum_energy_now + energy_now
sum_energy_full = sum_energy_full + energy_full
sum_energy_percentage = sum_energy_percentage + energy_percentage
sum_rate_voltage = sum_rate_voltage + (rate_voltage or 0)
sum_rate_power = sum_rate_power + (rate_power or 0)
sum_rate_energy = sum_rate_energy + (rate_power or (((rate_voltage or 0) * (rate_current or 0)) / 1e6))
sum_energy_now = sum_energy_now + (energy_now or 0)
sum_energy_full = sum_energy_full + (energy_full or 0)
end
end
bat_now.status = bat_now.n_status[1]
bat_now.ac_status = first_line(string.format("/sys/class/power_supply/%s/online", ac)) or "N/A"
bat_now.ac_status = tonumber(first_line(string.format("%s%s/online", pspath, ac))) or "N/A"
-- update {perc,time,watt} iff rate > 0 and battery not full
if (sum_rate_current > 0 or sum_rate_power > 0) and not (bat_now.status == "Full")
then
if bat_now.status ~= "N/A" then
-- 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
local rate_time = 0
local div = (sum_rate_power > 0 and sum_rate_power) or sum_rate_current
if bat_now.status == "Charging" then
rate_time = (sum_energy_full - sum_energy_now) / (sum_rate_power or sum_rate_current)
elseif bat_now.status == "Discharging" then
rate_time = sum_energy_now / (sum_rate_power or sum_rate_current)
rate_time = (sum_energy_full - sum_energy_now) / div
else -- Discharging
rate_time = sum_energy_now / div
end
if 0 < rate_time and rate_time < 0.01 then -- check for magnitude discrepancies (#199)
rate_time_magnitude = math.abs(math.floor(math.log10(rate_time)))
rate_time = rate_time * 10^(rate_time_magnitude - 2)
end
local hours = math.floor(rate_time)
local minutes = math.floor((rate_time - hours) * 60)
local watt = sum_rate_power / 1e6
bat_now.perc = string.format("%d", math.min(100, sum_energy_percentage / #batteries))
bat_now.perc = math.floor(math.min(100, (sum_energy_now / sum_energy_full) * 100))
bat_now.time = string.format("%02d:%02d", hours, minutes)
bat_now.watt = string.format("%.2fW", watt)
bat_now.watt = tonumber(string.format("%.2f", sum_rate_energy / 1e6))
elseif bat_now.status ~= "Full" and sum_rate_power == 0 and bat_now.ac_status == 1 then
bat_now.perc = math.floor(math.min(100, (sum_energy_now / sum_energy_full) * 100))
bat_now.time = "00:00"
bat_now.watt = 0
elseif bat_now.status == "Full" then
bat_now.perc = 100
bat_now.time = "00:00"
bat_now.watt = 0
end
end
widget = bat.widget
settings()
-- notifications for low and critical states
if bat_now.status == "Discharging" and notify == "on" and bat_now.perc then
local nperc = tonumber(bat_now.perc) or 100
if nperc <= 5 then
if notify == "on" and type(bat_now.perc) == "number" and bat_now.status == "Discharging" then
if bat_now.perc <= 5 then
bat.id = naughty.notify({
preset = bat_notification_critical_preset,
replaces_id = bat.id,
replaces_id = bat.id
}).id
elseif nperc <= 15 then
elseif bat_now.perc <= 15 then
bat.id = naughty.notify({
preset = bat_notification_low_preset,
replaces_id = bat.id,
replaces_id = bat.id
}).id
end
end
end
newtimer(battery, timeout, update)
newtimer(battery, timeout, bat.update)
return setmetatable(bat, { __index = bat.widget })
end

View File

@ -27,15 +27,15 @@ local setmetatable = setmetatable
local calendar = {}
local cal_notification = nil
function calendar:hide()
function calendar.hide()
if cal_notification ~= nil then
naughty.destroy(cal_notification)
cal_notification = nil
end
end
function calendar:show(t_out, inc_offset, scr)
calendar:hide()
function calendar.show(t_out, inc_offset, scr)
calendar.hide()
local f, c_text
local offs = inc_offset or 0
@ -96,7 +96,7 @@ function calendar:show(t_out, inc_offset, scr)
})
end
function calendar:attach(widget, args)
function calendar.attach(widget, args)
local args = args or {}
calendar.cal = args.cal or "/usr/bin/cal"
@ -116,16 +116,16 @@ function calendar:attach(widget, args)
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::enter", function () calendar.show(0, 0, calendar.scr_pos) end)
widget:connect_signal("mouse::leave", function () calendar.hide() end)
widget:buttons(awful.util.table.join(awful.button({ }, 1, function ()
calendar:show(0, -1, calendar.scr_pos) end),
calendar.show(0, -1, calendar.scr_pos) end),
awful.button({ }, 3, function ()
calendar:show(0, 1, calendar.scr_pos) end),
calendar.show(0, 1, calendar.scr_pos) end),
awful.button({ }, 4, function ()
calendar:show(0, -1, calendar.scr_pos) end),
calendar.show(0, -1, calendar.scr_pos) end),
awful.button({ }, 5, function ()
calendar:show(0, 1, calendar.scr_pos) end)))
calendar.show(0, 1, calendar.scr_pos) end)))
end
return setmetatable(calendar, { __call = function(_, ...) return create(...) end })

View File

@ -1,82 +0,0 @@
--[[
Licensed under GNU General Public License v2
* (c) 2014, Aaron Lebo
--]]
local newtimer = require("lain.helpers").newtimer
local json = require("lain.util").dkjson
local wibox = require("wibox")
local string = { format = string.format }
local tonumber = tonumber
-- Crypto currencies widget
-- lain.widgets.contrib.ccurr
local ccurr = {}
-- Currently gets
-- * BTC/USD
-- * DOGE/USD
-- using Coinbase and Cryptsy APIs.
-- requires http://dkolf.de/src/dkjson-lua.fsl/home
-- based upon http://awesome.naquadah.org/wiki/Bitcoin_Price_Widget
local function get(url)
local f = io.popen('curl -m 5 -s "' .. url .. '"')
if not f then
return 0
else
local s = f:read("*all")
f:close()
return s
end
end
local function parse(j)
local obj, pos, err = json.decode(j, 1, nil)
if err then
return nil
else
return obj
end
end
local function worker(args)
local args = args or {}
local timeout = args.timeout or 600
local btc_url = args.btc_url or "https://coinbase.com/api/v1/prices/buy"
local doge_url = args.doge_url or "http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=132"
local settings = args.settings or function() end
ccurr.widget = wibox.widget.textbox('')
local function update()
price_now = {
btc = "N/A",
doge = "N/A"
}
btc = parse(get(btc_url))
doge = parse(get(doge_url))
if btc and doge then
price_now.btc = tonumber(btc["subtotal"]["amount"])
price_now.doge = tonumber(doge["return"]["markets"]["DOGE"]["lasttradeprice"])
price_now.doge = string.format("%.4f", price_now.btc * price_now.doge)
end
widget = ccurr.widget
settings()
end
newtimer("ccurr", timeout, update)
return ccurr.widget
end
return setmetatable(ccurr, { __call = function(_, ...) return worker(...) end })

95
widgets/contrib/gpmdp.lua Normal file
View File

@ -0,0 +1,95 @@
--[[
Licensed under GNU General Public License v2
* (c) 2016, Alexandre Terrien
--]]
local helpers = require("lain.helpers")
local json = require("lain.util.dkjson")
local pread = require("awful.util").pread
local naughty = require("naughty")
local wibox = require("wibox")
local mouse = mouse
local os = { getenv = os.getenv }
local setmetatable = setmetatable
-- Google Play Music Desktop infos
-- lain.widget.contrib.gpmdp
local gpmdp = {}
local function worker(args)
local args = args or {}
local timeout = args.timeout or 2
local notify = args.notify or "off"
local followmouse = args.followmouse or false
local file_location = args.file_location or
os.getenv("HOME") .. "/.config/Google Play Music Desktop Player/json_store/playback.json"
local settings = args.settings or function() end
gpmdp.widget = wibox.widget.textbox('')
gpmdp_notification_preset = {
title = "Now playing",
timeout = 6
}
helpers.set_map("gpmdp_current", nil)
function gpmdp.update()
file, err = io.open(file_location, "r")
if not file
then
gpm_now = { running = false, playing = false }
else
dict, pos, err = json.decode(file:read "*a", 1, nil)
file:close()
gpm_now = {}
gpm_now.artist = dict.song.artist
gpm_now.album = dict.song.album
gpm_now.title = dict.song.title
gpm_now.cover_url = dict.song.albumArt
gpm_now.playing = dict.playing
end
if (pread("pidof 'Google Play Music Desktop Player'") ~= '') then
gpm_now.running = true
else
gpm_now.running = false
end
gpmdp_notification_preset.text = string.format("%s (%s) - %s", gpm_now.artist, gpm_now.album, gpm_now.title)
widget = gpmdp.widget
settings()
if gpm_now.playing
then
if notify == "on" and gpm_now.title ~= helpers.get_map("gpmdp_current")
then
helpers.set_map("gpmdp_current", gpm_now.title)
os.execute("curl " .. gpm_now.cover_url .. " -o /tmp/gpmcover.png")
if followmouse then
gpmdp_notification_preset.screen = mouse.screen
end
gpmdp.id = naughty.notify({
preset = gpmdp_notification_preset,
icon = "/tmp/gpmcover.png",
replaces_id = gpmdp.id,
}).id
end
elseif not gpm_now.running
then
helpers.set_map("gpmdp_current", nil)
end
end
helpers.newtimer("gpmdp", timeout, gpmdp.update)
return setmetatable(gpmdp, { __index = gpmdp.widget })
end
return setmetatable(gpmdp, { __call = function(_, ...) return worker(...) end })

View File

@ -21,7 +21,6 @@ local active = false -- true if redshift is active
local running = false -- true if redshift was initialized
local update_fnct = function() end -- Function that is run each time redshift is toggled. See redshift:attach().
local function init()
-- As there is no way to determine if redshift was previously
-- toggled off (i.e Awesome on-the-fly restart), kill redshift to make sure

View File

@ -25,29 +25,34 @@ local task = {}
local task_notification = nil
function task:hide()
function findLast(haystack, needle)
local i=haystack:match(".*"..needle.."()")
if i==nil then return nil else return i-1 end
end
function task.hide()
if task_notification ~= nil then
naughty.destroy(task_notification)
task_notification = nil
end
end
function task:show(scr_pos)
task:hide()
function task.show(scr_pos)
task.hide()
local f, c_text
local f, c_text, scrp
if task.followmouse then
local scrp = mouse.screen
scrp = mouse.screen
else
local scrp = scr_pos or task.scr_pos
scrp = scr_pos or task.scr_pos
end
f = io.popen('task')
f = io.popen('task ' .. task.cmdline)
c_text = "<span font='"
.. task.font .. " "
.. task.font_size .. "'>"
.. f:read("*all"):gsub("\n*$", "")
.. awful.util.escape(f:read("*all"):gsub("\n*$", ""))
.. "</span>"
f:close()
@ -62,7 +67,7 @@ function task:show(scr_pos)
})
end
function task:prompt_add()
function task.prompt_add()
awful.prompt.run({ prompt = "Add task: " },
mypromptbox[mouse.screen].widget,
function (...)
@ -70,7 +75,7 @@ function task:prompt_add()
c_text = "\n<span font='"
.. task.font .. " "
.. task.font_size .. "'>"
.. f:read("*all")
.. awful.util.escape(f:read("*all"))
.. "</span>"
f:close()
@ -87,7 +92,7 @@ function task:prompt_add()
awful.util.getdir("cache") .. "/history_task_add")
end
function task:prompt_search()
function task.prompt_search()
awful.prompt.run({ prompt = "Search task: " },
mypromptbox[mouse.screen].widget,
function (...)
@ -102,7 +107,7 @@ function task:prompt_search()
c_text = "<span font='"
.. task.font .. " "
.. task.font_size .. "'>"
.. c_text
.. awful.util.escape(c_text)
.. "</span>"
end
@ -121,24 +126,25 @@ function task:prompt_search()
awful.util.getdir("cache") .. "/history_task")
end
function task:attach(widget, args)
function task.attach(widget, args)
local args = args or {}
task.font_size = tonumber(args.font_size) or 12
task.font = beautiful.font:sub(beautiful.font:find(""),
beautiful.font:find(" "))
task.font = args.font or beautiful.font:sub(beautiful.font:find(""),
findLast(beautiful.font, " "))
task.fg = args.fg or beautiful.fg_normal or "#FFFFFF"
task.bg = args.bg or beautiful.bg_normal or "#FFFFFF"
task.position = args.position or "top_right"
task.timeout = args.timeout or 7
task.scr_pos = args.scr_pos or 1
task.followmouse = args.followmouse or false
task.cmdline = args.cmdline or "next"
task.notify_icon = icons_dir .. "/taskwarrior/task.png"
task.notify_icon_small = icons_dir .. "/taskwarrior/tasksmall.png"
widget:connect_signal("mouse::enter", function () task:show(task.scr_pos) end)
widget:connect_signal("mouse::leave", function () task:hide() end)
widget:connect_signal("mouse::enter", function () task.show(task.scr_pos) end)
widget:connect_signal("mouse::leave", function () task.hide() end)
end
return setmetatable(task, { __call = function(_, ...) return create(...) end })

View File

@ -16,7 +16,6 @@ local tonumber = tonumber
local setmetatable = setmetatable
local smapi = {}
local apipath = "/sys/devices/platform/smapi"
-- Most are readable values, but some can be written to (not implemented, yet?)

View File

@ -28,26 +28,29 @@ local setmetatable = setmetatable
local fs = {}
local fs_notification = nil
function fs:hide()
function fs.hide()
if fs_notification ~= nil then
naughty.destroy(fs_notification)
fs_notification = nil
end
end
function fs:show(t_out)
fs:hide()
function fs.show(seconds, options, scr)
fs.hide()
local ws = helpers.read_pipe(helpers.scripts_dir .. "dfs"):gsub("\n*$", "")
local cmd = (options and string.format("dfs %s", options)) or "dfs"
local ws = helpers.read_pipe(helpers.scripts_dir .. cmd):gsub("\n*$", "")
if fs.followmouse then
fs.notification_preset.screen = mouse.screen
elseif scr then
fs.notification_preset.screen = scr
end
fs_notification = naughty.notify({
preset = fs.notification_preset,
text = ws,
timeout = t_out
timeout = seconds or 5
})
end
@ -58,6 +61,8 @@ local function worker(args)
local args = args or {}
local timeout = args.timeout or 600
local partition = args.partition or "/"
local showpopup = args.showpopup or "on"
local notify = args.notify or "on"
local settings = args.settings or function() end
fs.followmouse = args.followmouse or false
@ -92,10 +97,11 @@ local function worker(args)
fs_now.size_mb = tonumber(fs_info[partition .. " size_mb"]) or 0
fs_now.size_gb = tonumber(fs_info[partition .. " size_gb"]) or 0
notification_preset = fs.notification_preset
widget = fs.widget
settings()
if fs_now.used >= 99 and not helpers.get_map(partition)
if notify == "on" and fs_now.used >= 99 and not helpers.get_map(partition)
then
naughty.notify({
title = "warning",
@ -110,8 +116,10 @@ local function worker(args)
end
end
if showpopup == "on" then
fs.widget:connect_signal('mouse::enter', function () fs:show(0) end)
fs.widget:connect_signal('mouse::leave', function () fs:hide() end)
end
helpers.newtimer(partition, timeout, update)

View File

@ -41,8 +41,7 @@ local function worker(args)
helpers.set_map(mail, 0)
if not is_plain
then
if not is_plain then
password = helpers.read_pipe(password):gsub("\n", "")
end

View File

@ -39,8 +39,7 @@ local function worker(args)
maildir.widget = wibox.widget.textbox('')
function update()
if ext_mail_cmd ~= nil
then
if ext_mail_cmd then
awful.util.spawn(ext_mail_cmd)
end
@ -75,6 +74,7 @@ local function worker(args)
p:close()
newmail = "no mail"
-- Count the total number of mails irrespective of where it was found
total = 0

View File

@ -46,6 +46,7 @@ local function worker(args)
mem_now.used = mem_now.total - (mem_now.free + mem_now.buf + mem_now.cache)
mem_now.swapused = mem_now.swap - mem_now.swapf
mem_now.perc = math.floor(mem_now.used / mem_now.total * 100)
widget = mem.widget
settings()

View File

@ -37,6 +37,7 @@ local function worker(args)
local music_dir = args.music_dir or os.getenv("HOME") .. "/Music"
local cover_size = args.cover_size or 100
local default_art = args.default_art or ""
local notify = args.notify or "on"
local followmouse = args.followmouse or false
local echo_cmd = args.echo_cmd or "echo"
local settings = args.settings or function() end
@ -57,6 +58,12 @@ local function worker(args)
function mpd.update()
async.request(echo .. " | curl --connect-timeout 1 -fsm 3 " .. mpdh, function (f)
mpd_now = {
random_mode = false,
single_mode = false,
repeat_mode = false,
consume_mode = false,
pls_pos = "N/A",
pls_len = "N/A",
state = "N/A",
file = "N/A",
name = "N/A",
@ -79,6 +86,12 @@ local function worker(args)
elseif k == "Date" then mpd_now.date = escape_f(v)
elseif k == "Time" then mpd_now.time = v
elseif k == "elapsed" then mpd_now.elapsed = string.match(v, "%d+")
elseif k == "song" then mpd_now.pls_pos = v
elseif k == "playlistlength" then mpd_now.pls_len = v
elseif k == "repeat" then mpd_now.repeat_mode = v ~= "0"
elseif k == "single" then mpd_now.single_mode = v ~= "0"
elseif k == "random" then mpd_now.random_mode = v ~= "0"
elseif k == "consume" then mpd_now.consume_mode = v ~= "0"
end
end
end
@ -90,7 +103,7 @@ local function worker(args)
if mpd_now.state == "play"
then
if mpd_now.title ~= helpers.get_map("current mpd track")
if notify == "on" and mpd_now.title ~= helpers.get_map("current mpd track")
then
helpers.set_map("current mpd track", mpd_now.title)

View File

@ -128,12 +128,12 @@ local function worker(args)
net_now.sent = string.gsub(string.format('%.1f', net_now.sent), ',', '.')
net_now.received = string.gsub(string.format('%.1f', net_now.received), ',', '.')
widget = net.widget
settings()
net.last_t = total_t
net.last_r = total_r
end
widget = net.widget
settings()
end
helpers.newtimer(iface, timeout, update)

View File

@ -10,7 +10,8 @@ local read_pipe = require("lain.helpers").read_pipe
local newtimer = require("lain.helpers").newtimer
local wibox = require("wibox")
local string = { match = string.match,
local string = { gmatch = string.gmatch,
match = string.match,
format = string.format }
local setmetatable = setmetatable
@ -25,7 +26,7 @@ local function worker(args)
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'")
pulseaudio.cmd = args.cmd or string.format("pacmd list-sinks | sed -n -e '0,/*/d' -e '/base volume/d' -e '/volume:/p' -e '/muted:/p' -e '/device\\.string/p'")
pulseaudio.widget = wibox.widget.textbox('')
function pulseaudio.update()
@ -33,9 +34,19 @@ local function worker(args)
local s = read_pipe(pulseaudio.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+)")
volume_now.index = string.match(s, "index: (%S+)") or "N/A"
volume_now.sink = string.match(s, "device.string = \"(%S+)\"") or "N/A"
volume_now.muted = string.match(s, "muted: (%S+)") or "N/A"
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"
widget = pulseaudio.widget
settings()

View File

@ -27,6 +27,7 @@ local setmetatable = setmetatable
-- lain.widgets.pulsebar
local pulsebar = {
sink = 0,
step = "1%",
colors = {
background = beautiful.bg_normal,
@ -105,6 +106,7 @@ local function worker(args)
pulsebar.colors = args.colors or pulsebar.colors
pulsebar.notifications = args.notifications or pulsebar.notifications
pulsebar.sink = args.sink or 0
pulsebar.step = args.step or pulsebar.step
pulsebar.followmouse = args.followmouse or false
pulsebar.bar = awful.widget.progressbar()
@ -151,6 +153,22 @@ local function worker(args)
pulsebar.bar:buttons(awful.util.table.join (
awful.button({}, 1, function()
awful.util.spawn(pulsebar.mixer)
end),
awful.button({}, 2, function()
awful.util.spawn(string.format("pactl set-sink-volume %d 100%%", pulsebar.sink))
pulsebar.update()
end),
awful.button({}, 3, function()
awful.util.spawn(string.format("pactl set-sink-mute %d toggle", pulsebar.sink))
pulsebar.update()
end),
awful.button({}, 4, function()
awful.util.spawn(string.format("pactl set-sink-volume %d +%s", pulsebar.sink, pulsebar.step))
pulsebar.update()
end),
awful.button({}, 5, function()
awful.util.spawn(string.format("pactl set-sink-volume %d -%s", pulsebar.sink, pulsebar.step))
pulsebar.update()
end)
))

View File

@ -108,7 +108,7 @@ local function worker(args)
local pos, err
weather_now, pos, err = json.decode(f, 1, nil)
if not err and weather_now and tonumber(weather_now["cod"]) == 200 then
if not err and type(weather_now) == "table" and tonumber(weather_now["cod"]) == 200 then
weather.notification_text = ''
for i = 1, weather_now["cnt"] do
weather.notification_text = weather.notification_text ..
@ -128,7 +128,7 @@ local function worker(args)
local pos, err, icon
weather_now, pos, err = json.decode(f, 1, nil)
if not err and weather_now and tonumber(weather_now["cod"]) == 200 then
if not err and type(weather_now) == "table" and tonumber(weather_now["cod"]) == 200 then
-- weather icon based on localtime
local now = os.time()
local sunrise = tonumber(weather_now["sys"]["sunrise"])

2
wiki

@ -1 +1 @@
Subproject commit 25dd1a2ec44da832d06ded29f393d716e4b54783
Subproject commit d0df450d05655c5d8f724c42dc6b5d18b3676a60