Added settings.lua

This commit is contained in:
Mutzi 2023-01-25 20:10:58 +01:00
parent 80cc950271
commit 5cf452e053
38 changed files with 3880 additions and 3450 deletions

View File

@ -11,44 +11,44 @@
--]]
local awful = require("awful")
local theme = require("beautiful")
local utils = require("menubar.utils")
local wibox = require("wibox")
local awful = require('awful')
local theme = require('beautiful')
local utils = require('menubar.utils')
local wibox = require('wibox')
local io = io
local io = io
local ipairs = ipairs
local mouse = mouse
local os = os
local mouse = mouse
local os = os
local string = string
local screen = screen
local table = table
local table = table
-- Desktop icons
-- freedesktop.desktop
local desktop = {
-- Default desktop basic icons
baseicons = {
[1] = {
label = "This PC",
icon = "computer",
onclick = "computer://"
},
[2] = {
label = "Home",
icon = "user-home",
onclick = os.getenv("HOME")
},
[3] = {
label = "Trash",
icon = "user-trash",
onclick = "trash://"
}
},
-- Default parameters
iconsize = { width = 48, height = 48 },
labelsize = { width = 140, height = 20 },
margin = { x = 20, y = 20 },
-- Default desktop basic icons
baseicons = {
[1] = {
label = 'This PC',
icon = 'computer',
onclick = 'computer://',
},
[2] = {
label = 'Home',
icon = 'user-home',
onclick = os.getenv('HOME'),
},
[3] = {
label = 'Trash',
icon = 'user-trash',
onclick = 'trash://',
},
},
-- Default parameters
iconsize = { width = 48, height = 48 },
labelsize = { width = 140, height = 20 },
margin = { x = 20, y = 20 },
}
-- MIME types list
@ -59,12 +59,14 @@ desktop.current_pos = {}
-- @return iterator on input pipe
local function pipelines(...)
local f = assert(io.popen(...))
return function ()
local data = f:read()
if data == nil then f:close() end
return data
end
local f = assert(io.popen(...))
return function()
local data = f:read()
if data == nil then
f:close()
end
return data
end
end
-- Adds an icon to desktop
@ -73,187 +75,198 @@ end
-- @param icon icon string file path
-- @param onclick function to execute on click
function desktop.add_single_icon(args, label, icon, onclick)
local s = args.screen
local dcp = desktop.current_pos
local s = args.screen
local dcp = desktop.current_pos
-- define icon dimensions and position
if not dcp[s] then
dcp[s] = { x = (screen[s].geometry.x + args.iconsize.width + args.margin.x), y = screen[s].geometry.y + 20 + args.margin.y }
end
-- define icon dimensions and position
if not dcp[s] then
dcp[s] = {
x = (screen[s].geometry.x + args.iconsize.width + args.margin.x),
y = screen[s].geometry.y + 20 + args.margin.y,
}
end
local tot_height = (icon and args.iconsize.height or 0) + (label and args.labelsize.height or 0)
if tot_height == 0 then return end
local tot_height = (icon and args.iconsize.height or 0) + (label and args.labelsize.height or 0)
if tot_height == 0 then
return
end
if dcp[s].y + tot_height > screen[s].geometry.y + screen[s].geometry.height - 20 - args.margin.y then
dcp[s].x = dcp[s].x + args.labelsize.width + args.iconsize.width + args.margin.x
dcp[s].y = 20 + args.margin.y
end
if dcp[s].y + tot_height > screen[s].geometry.y + screen[s].geometry.height - 20 - args.margin.y then
dcp[s].x = dcp[s].x + args.labelsize.width + args.iconsize.width + args.margin.x
dcp[s].y = 20 + args.margin.y
end
local common = { screen = s, bg = "#00000000", visible = true, type = "desktop" }
local common = { screen = s, bg = '#00000000', visible = true, type = 'desktop' }
-- create icon container
if icon then
common.width = args.iconsize.width
common.height = args.iconsize.height
common.x = dcp[s].x
common.y = dcp[s].y
-- create icon container
if icon then
common.width = args.iconsize.width
common.height = args.iconsize.height
common.x = dcp[s].x
common.y = dcp[s].y
icon = wibox.widget {
image = icon,
resize = false,
widget = wibox.widget.imagebox
}
icon = wibox.widget({
image = icon,
resize = false,
widget = wibox.widget.imagebox,
})
icon:buttons(awful.button({ }, 1, nil, onclick))
icon:buttons(awful.button({}, 1, nil, onclick))
icon_container = wibox(common)
icon_container:set_widget(icon)
icon_container = wibox(common)
icon_container:set_widget(icon)
dcp[s].y = dcp[s].y + args.iconsize.height + 5
end
dcp[s].y = dcp[s].y + args.iconsize.height + 5
end
-- create label container
if label then
common.width = args.labelsize.width
common.height = args.labelsize.height
common.x = dcp[s].x - (args.labelsize.width/2) + args.iconsize.width/2
common.y = dcp[s].y
-- create label container
if label then
common.width = args.labelsize.width
common.height = args.labelsize.height
common.x = dcp[s].x - (args.labelsize.width / 2) + args.iconsize.width / 2
common.y = dcp[s].y
caption = wibox.widget {
text = label,
align = "center",
forced_width = common.width,
forced_height = common.height,
ellipsize = "middle",
widget = wibox.widget.textbox
}
caption = wibox.widget({
text = label,
align = 'center',
forced_width = common.width,
forced_height = common.height,
ellipsize = 'middle',
widget = wibox.widget.textbox,
})
caption:buttons(awful.button({ }, 1, onclick))
caption_container = wibox(common)
caption_container:set_widget(caption)
end
caption:buttons(awful.button({}, 1, onclick))
caption_container = wibox(common)
caption_container:set_widget(caption)
end
dcp[s].y = dcp[s].y + args.labelsize.height + args.margin.y
dcp[s].y = dcp[s].y + args.labelsize.height + args.margin.y
desktop.current_pos = dcp
desktop.current_pos = dcp
return dcp
return dcp
end
-- Adds base icons (This PC, Trash, etc) to desktop
-- @param args settings from desktop.add_icons
function desktop.add_base_icons(args)
for _,base in ipairs(args.baseicons) do
desktop.add_single_icon(args, base.label, utils.lookup_icon(base.icon), function()
awful.spawn(string.format("%s '%s'", args.open_with, base.onclick))
end)
end
for _, base in ipairs(args.baseicons) do
desktop.add_single_icon(args, base.label, utils.lookup_icon(base.icon), function()
awful.spawn(string.format("%s '%s'", args.open_with, base.onclick))
end)
end
end
-- Looks up a suitable icon for filename
-- @param filename string file name
-- @return icon file path (string)
function desktop.lookup_file_icon(filename)
-- load system MIME types
if #mime_types == 0 then
for line in io.lines("/etc/mime.types") do
if not line:find("^#") then
local parsed = {}
for w in line:gmatch("[^%s]+") do
table.insert(parsed, w)
end
if #parsed > 1 then
for i = 2, #parsed do
mime_types[parsed[i]] = parsed[1]:gsub("/", "-")
end
end
end
end
end
-- load system MIME types
if #mime_types == 0 then
for line in io.lines('/etc/mime.types') do
if not line:find('^#') then
local parsed = {}
for w in line:gmatch('[^%s]+') do
table.insert(parsed, w)
end
if #parsed > 1 then
for i = 2, #parsed do
mime_types[parsed[i]] = parsed[1]:gsub('/', '-')
end
end
end
end
end
-- try to search a possible icon among standards
local extension = filename:match("%a+$")
local mime = mime_types[extension] or ""
local mime_family = mime:match("^%a+") or ""
-- try to search a possible icon among standards
local extension = filename:match('%a+$')
local mime = mime_types[extension] or ''
local mime_family = mime:match('^%a+') or ''
local possible_filenames = {
mime, "gnome-mime-" .. mime,
mime_family, "gnome-mime-" .. mime_family,
extension
}
local possible_filenames = {
mime,
'gnome-mime-' .. mime,
mime_family,
'gnome-mime-' .. mime_family,
extension,
}
for i, filename in ipairs(possible_filenames) do
local icon = utils.lookup_icon(filename)
if icon then return icon end
end
for i, filename in ipairs(possible_filenames) do
local icon = utils.lookup_icon(filename)
if icon then
return icon
end
end
-- if we don"t find ad icon, then pretend is a plain text file
return utils.lookup_icon("text-x-generic")
-- if we don"t find ad icon, then pretend is a plain text file
return utils.lookup_icon('text-x-generic')
end
-- Parse subdirectories and files list from input directory
-- @input dir directory to parse (string)
-- @return files table with found entries
function desktop.parse_dirs_and_files(dir)
local files = {}
local paths = pipelines('find '..dir..' -maxdepth 1 -type d |sort|tail -n +1')
for path in paths do
if path:match("[^/]+$") then
local file = {}
file.filename = path:match("[^/]+$")
file.path = path
file.show = true
file.icon = utils.lookup_icon("folder")
table.insert(files, file)
end
end
local paths = pipelines('find '..dir..' -maxdepth 1 -type f')
for path in paths do
if not path:find("%.desktop$") then
local file = {}
file.filename = path:match("[^/]+$")
file.path = path
file.show = true
file.icon = desktop.lookup_file_icon(file.filename)
table.insert(files, file)
end
end
return files
local files = {}
local paths = pipelines('find ' .. dir .. ' -maxdepth 1 -type d |sort|tail -n +1')
for path in paths do
if path:match('[^/]+$') then
local file = {}
file.filename = path:match('[^/]+$')
file.path = path
file.show = true
file.icon = utils.lookup_icon('folder')
table.insert(files, file)
end
end
local paths = pipelines('find ' .. dir .. ' -maxdepth 1 -type f')
for path in paths do
if not path:find('%.desktop$') then
local file = {}
file.filename = path:match('[^/]+$')
file.path = path
file.show = true
file.icon = desktop.lookup_file_icon(file.filename)
table.insert(files, file)
end
end
return files
end
-- Adds subdirectories and files icons from args.dir
-- @param args settings from desktop.add_icons
function desktop.add_dirs_and_files_icons(args)
for _, file in ipairs(desktop.parse_dirs_and_files(args.dir)) do
if file.show then
local label = args.showlabels and file.filename or nil
local onclick = function () awful.spawn(string.format("%s '%s'", args.open_with, file.path)) end
desktop.add_single_icon(args, label, file.icon, onclick)
end
end
for _, file in ipairs(desktop.parse_dirs_and_files(args.dir)) do
if file.show then
local label = args.showlabels and file.filename or nil
local onclick = function()
awful.spawn(string.format("%s '%s'", args.open_with, file.path))
end
desktop.add_single_icon(args, label, file.icon, onclick)
end
end
end
-- Main function, adds base, directory and files icons
-- @param args user defined settings, with fallback on defaults
function desktop.add_icons(args)
args = args or {}
args.screen = args.screen or mouse.screen
args.dir = args.dir or os.getenv("HOME") .. "/Desktop"
args.showlabels = args.showlabel or true
args.open_with = args.open_with or "xdg_open"
args.baseicons = args.baseicons or desktop.baseicons
args.iconsize = args.iconsize or desktop.iconsize
args.labelsize = args.labelsize or desktop.labelsize
args.margin = args.margin or desktop.margin
args = args or {}
args.screen = args.screen or mouse.screen
args.dir = args.dir or os.getenv('HOME') .. '/Desktop'
args.showlabels = args.showlabel or true
args.open_with = args.open_with or 'xdg_open'
args.baseicons = args.baseicons or desktop.baseicons
args.iconsize = args.iconsize or desktop.iconsize
args.labelsize = args.labelsize or desktop.labelsize
args.margin = args.margin or desktop.margin
-- trying to fallback on Adwaita if theme.icon_theme is not defined
-- if Adwaita is missing too, no icons will be shown
if not theme.icon_theme then
theme.icon_theme = args.icon_theme or "Adwaita"
end
-- trying to fallback on Adwaita if theme.icon_theme is not defined
-- if Adwaita is missing too, no icons will be shown
if not theme.icon_theme then
theme.icon_theme = args.icon_theme or 'Adwaita'
end
desktop.add_base_icons(args)
desktop.add_dirs_and_files_icons(args)
desktop.add_base_icons(args)
desktop.add_dirs_and_files_icons(args)
end
return desktop

View File

@ -10,6 +10,6 @@
--]]
return {
desktop = require("freedesktop.desktop"),
menu = require("freedesktop.menu")
desktop = require('freedesktop.desktop'),
menu = require('freedesktop.menu'),
}

View File

@ -1,4 +1,3 @@
--[[
Awesome-Freedesktop
@ -12,15 +11,15 @@
--]]
local Gio = require("lgi").Gio
local awful_menu = require("awful.menu")
local menu_gen = require("menubar.menu_gen")
local menu_utils = require("menubar.utils")
local Gio = require('lgi').Gio
local awful_menu = require('awful.menu')
local menu_gen = require('menubar.menu_gen')
local menu_utils = require('menubar.utils')
local io, pairs, string, table, os = io, pairs, string, table, os
-- Expecting a wm_name of awesome omits too many applications and tools
menu_utils.wm_name = ""
menu_utils.wm_name = ''
-- Menu
-- freedesktop.menu
@ -30,15 +29,15 @@ local menu = {}
-- @tparam string path The directory path
-- @treturn boolean True if path exists and is a directory
function menu.is_dir(path)
return Gio.File.new_for_path(path):query_file_type({}) == "DIRECTORY"
return Gio.File.new_for_path(path):query_file_type({}) == 'DIRECTORY'
end
-- Remove non existent paths in order to avoid issues
local existent_paths = {}
for k,v in pairs(menu_gen.all_menu_dirs) do
if menu.is_dir(v) then
table.insert(existent_paths, v)
end
for k, v in pairs(menu_gen.all_menu_dirs) do
if menu.is_dir(v) then
table.insert(existent_paths, v)
end
end
menu_gen.all_menu_dirs = existent_paths
@ -46,76 +45,84 @@ menu_gen.all_menu_dirs = existent_paths
-- @param tab a given table
-- @param val the element to search for
-- @return true if the given string is found within the search table; otherwise, false if not
function menu.has_value (tab, val)
for index, value in pairs(tab) do
if val:find(value) then
return true
end
end
return false
function menu.has_value(tab, val)
for index, value in pairs(tab) do
if val:find(value) then
return true
end
end
return false
end
-- Use MenuBar parsing utils to build a menu for Awesome
-- @return awful.menu
function menu.build(args)
local args = args or {}
local before = args.before or {}
local after = args.after or {}
local skip_items = args.skip_items or {}
local sub_menu = args.sub_menu or false
local args = args or {}
local before = args.before or {}
local after = args.after or {}
local skip_items = args.skip_items or {}
local sub_menu = args.sub_menu or false
local result = {}
local _menu = awful_menu({ items = before })
local result = {}
local _menu = awful_menu({ items = before })
menu_gen.generate(function(entries)
-- Add category icons
for k, v in pairs(menu_gen.all_categories) do
table.insert(result, { k, {}, v.icon })
end
menu_gen.generate(function(entries)
-- Add category icons
for k, v in pairs(menu_gen.all_categories) do
table.insert(result, { k, {}, v.icon })
end
-- Get items table
for k, v in pairs(entries) do
for _, cat in pairs(result) do
if cat[1] == v.category then
if not menu.has_value(skip_items, v.name) then
table.insert(cat[2], { v.name, v.cmdline, v.icon })
end
break
end
end
end
-- Get items table
for k, v in pairs(entries) do
for _, cat in pairs(result) do
if cat[1] == v.category then
if not menu.has_value(skip_items, v.name) then
table.insert(cat[2], { v.name, v.cmdline, v.icon })
end
break
end
end
end
-- Cleanup things a bit
for i = #result, 1, -1 do
local v = result[i]
if #v[2] == 0 then
-- Remove unused categories
table.remove(result, i)
else
--Sort entries alphabetically (by name)
table.sort(v[2], function (a, b) return string.byte(a[1]) < string.byte(b[1]) end)
-- Replace category name with nice name
v[1] = menu_gen.all_categories[v[1]].name
end
end
-- Cleanup things a bit
for i = #result, 1, -1 do
local v = result[i]
if #v[2] == 0 then
-- Remove unused categories
table.remove(result, i)
else
--Sort entries alphabetically (by name)
table.sort(v[2], function(a, b)
return string.byte(a[1]) < string.byte(b[1])
end)
-- Replace category name with nice name
v[1] = menu_gen.all_categories[v[1]].name
end
end
-- Sort categories alphabetically also
table.sort(result, function(a, b) return string.byte(a[1]) < string.byte(b[1]) end)
-- Sort categories alphabetically also
table.sort(result, function(a, b)
return string.byte(a[1]) < string.byte(b[1])
end)
-- Add menu item to hold the generated menu
if sub_menu then
result = {{sub_menu, result}}
end
-- Add menu item to hold the generated menu
if sub_menu then
result = { { sub_menu, result } }
end
-- Add items to menu
for _, v in pairs(result) do _menu:add(v) end
for _, v in pairs(after) do _menu:add(v) end
end)
-- Add items to menu
for _, v in pairs(result) do
_menu:add(v)
end
for _, v in pairs(after) do
_menu:add(v)
end
end)
-- Hold the menu in the module
menu.menu = _menu
-- Hold the menu in the module
menu.menu = _menu
return _menu
return _menu
end
return menu

View File

@ -5,28 +5,27 @@
--]]
local spawn = require("awful.spawn")
local timer = require("gears.timer")
local debug = require("debug")
local io = { lines = io.lines,
open = io.open }
local pairs = pairs
local spawn = require('awful.spawn')
local timer = require('gears.timer')
local debug = require('debug')
local io = { lines = io.lines, open = io.open }
local pairs = pairs
local rawget = rawget
local tsort = table.sort
local tsort = table.sort
local unpack = unpack or table.unpack -- lua 5.1 retro-compatibility
-- Lain helper functions for internal use
-- lain.helpers
local helpers = {}
helpers.lain_dir = debug.getinfo(1, 'S').source:match[[^@(.*/).*$]]
helpers.icons_dir = helpers.lain_dir .. 'icons/'
helpers.lain_dir = debug.getinfo(1, 'S').source:match([[^@(.*/).*$]])
helpers.icons_dir = helpers.lain_dir .. 'icons/'
helpers.scripts_dir = helpers.lain_dir .. 'scripts/'
-- {{{ Modules loader
function helpers.wrequire(t, k)
return rawget(t, k) or require(t._NAME .. '.' .. k)
return rawget(t, k) or require(t._NAME .. '.' .. k)
end
-- }}}
@ -35,47 +34,51 @@ end
-- check if the file exists and is readable
function helpers.file_exists(path)
local file = io.open(path, "rb")
if file then file:close() end
return file ~= nil
local file = io.open(path, 'rb')
if file then
file:close()
end
return file ~= nil
end
-- get a table with all lines from a file
function helpers.lines_from(path)
local lines = {}
for line in io.lines(path) do
lines[#lines + 1] = line
end
return lines
local lines = {}
for line in io.lines(path) do
lines[#lines + 1] = line
end
return lines
end
-- get a table with all lines from a file matching regexp
function helpers.lines_match(regexp, path)
local lines = {}
for line in io.lines(path) do
if string.match(line, regexp) then
lines[#lines + 1] = line
end
end
return lines
local lines = {}
for line in io.lines(path) do
if string.match(line, regexp) then
lines[#lines + 1] = line
end
end
return lines
end
-- get first line of a file
function helpers.first_line(path)
local file, first = io.open(path, "rb"), nil
if file then
first = file:read("*l")
file:close()
end
return first
local file, first = io.open(path, 'rb'), nil
if file then
first = file:read('*l')
file:close()
end
return first
end
-- get first non empty line from a file
function helpers.first_nonempty_line(path)
for line in io.lines(path) do
if #line then return line end
end
return nil
for line in io.lines(path) do
if #line then
return line
end
end
return nil
end
-- }}}
@ -85,17 +88,19 @@ end
helpers.timer_table = {}
function helpers.newtimer(name, timeout, fun, nostart, stoppable)
if not name or #name == 0 then return end
name = (stoppable and name) or timeout
if not helpers.timer_table[name] then
helpers.timer_table[name] = timer({ timeout = timeout })
helpers.timer_table[name]:start()
end
helpers.timer_table[name]:connect_signal("timeout", fun)
if not nostart then
helpers.timer_table[name]:emit_signal("timeout")
end
return stoppable and helpers.timer_table[name]
if not name or #name == 0 then
return
end
name = (stoppable and name) or timeout
if not helpers.timer_table[name] then
helpers.timer_table[name] = timer({ timeout = timeout })
helpers.timer_table[name]:start()
end
helpers.timer_table[name]:connect_signal('timeout', fun)
if not nostart then
helpers.timer_table[name]:emit_signal('timeout')
end
return stoppable and helpers.timer_table[name]
end
-- }}}
@ -107,27 +112,25 @@ end
-- @param callback function to execute on cmd output
-- @return cmd PID
function helpers.async(cmd, callback)
return spawn.easy_async(cmd,
function (stdout, _, _, exit_code)
callback(stdout, exit_code)
end)
return spawn.easy_async(cmd, function(stdout, _, _, exit_code)
callback(stdout, exit_code)
end)
end
-- like above, but call spawn.easy_async with a shell
function helpers.async_with_shell(cmd, callback)
return spawn.easy_async_with_shell(cmd,
function (stdout, _, _, exit_code)
callback(stdout, exit_code)
end)
return spawn.easy_async_with_shell(cmd, function(stdout, _, _, exit_code)
callback(stdout, exit_code)
end)
end
-- run a command and execute a function on its output line by line
function helpers.line_callback(cmd, callback)
return spawn.with_line_callback(cmd, {
stdout = function (line)
callback(line)
end,
})
return spawn.with_line_callback(cmd, {
stdout = function(line)
callback(line)
end,
})
end
-- }}}
@ -137,11 +140,11 @@ end
helpers.map_table = {}
function helpers.set_map(element, value)
helpers.map_table[element] = value
helpers.map_table[element] = value
end
function helpers.get_map(element)
return helpers.map_table[element]
return helpers.map_table[element]
end
-- }}}
@ -150,52 +153,56 @@ end
-- 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
for _, i in pairs(tbl) do
if i == element then
return true
end
end
return false
end
-- iterate over table of records sorted by keys
function helpers.spairs(t)
-- collect the keys
local keys = {}
for k in pairs(t) do keys[#keys+1] = k end
-- collect the keys
local keys = {}
for k in pairs(t) do
keys[#keys + 1] = k
end
tsort(keys)
tsort(keys)
-- return the iterator function
local i = 0
return function()
i = i + 1
if keys[i] then
return keys[i], t[keys[i]]
end
end
-- return the iterator function
local i = 0
return function()
i = i + 1
if keys[i] then
return keys[i], t[keys[i]]
end
end
end
-- create the partition of singletons of a given set
-- example: the trivial partition set of {a, b, c}, is {{a}, {b}, {c}}
function helpers.trivial_partition_set(set)
local ss = {}
for _,e in pairs(set) do
ss[#ss+1] = {e}
end
return ss
local ss = {}
for _, e in pairs(set) do
ss[#ss + 1] = { e }
end
return ss
end
-- create the powerset of a given set
function helpers.powerset(s)
if not s then return {} end
local t = {{}}
for i = 1, #s do
for j = 1, #t do
t[#t+1] = {s[i],unpack(t[j])}
end
end
return t
if not s then
return {}
end
local t = { {} }
for i = 1, #s do
for j = 1, #t do
t[#t + 1] = { s[i], unpack(t[j]) }
end
end
return t
end
-- }}}

View File

@ -9,7 +9,7 @@
--]]
return {
layout = require("lain.layout"),
util = require("lain.util"),
widget = require("lain.widget")
layout = require('lain.layout'),
util = require('lain.util'),
widget = require('lain.widget'),
}

View File

@ -7,166 +7,184 @@
--]]
local floor = math.floor
local floor = math.floor
local screen = screen
local cascade = {
name = "cascade",
nmaster = 0,
offset_x = 32,
offset_y = 8,
tile = {
name = "cascadetile",
nmaster = 0,
ncol = 0,
mwfact = 0,
offset_x = 5,
offset_y = 32,
extra_padding = 0
}
name = 'cascade',
nmaster = 0,
offset_x = 32,
offset_y = 8,
tile = {
name = 'cascadetile',
nmaster = 0,
ncol = 0,
mwfact = 0,
offset_x = 5,
offset_y = 32,
extra_padding = 0,
},
}
local function do_cascade(p, tiling)
local t = p.tag or screen[p.screen].selected_tag
local wa = p.workarea
local cls = p.clients
local t = p.tag or screen[p.screen].selected_tag
local wa = p.workarea
local cls = p.clients
if #cls == 0 then return end
if #cls == 0 then
return
end
if not tiling then
-- Cascade windows.
if not tiling then
-- Cascade windows.
local num_c
if cascade.nmaster > 0 then
num_c = cascade.nmaster
else
num_c = t.master_count
end
local num_c
if cascade.nmaster > 0 then
num_c = cascade.nmaster
else
num_c = t.master_count
end
-- Opening a new window will usually force all existing windows to
-- get resized. This wastes a lot of CPU time. So let's set a lower
-- bound to "how_many": This wastes a little screen space but you'll
-- get a much better user experience.
local how_many = (#cls >= num_c and #cls) or num_c
-- 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)
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 = {}
-- 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
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
if g.width < 1 then
g.width = 1
end
if g.height < 1 then
g.height = 1
end
p.geometries[c] = g
end
else
-- 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.
p.geometries[c] = g
end
else
-- 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.
-- (1) (2) (3) (4)
-- +----------+---+ +----------+---+ +----------+---+ +----------+---+
-- | | | | | 3 | | | 4 | | +---+|
-- | | | -> | | | -> | +---++ -> | +---+|+
-- | 1 | 2 | | 1 +---++ | 1 | 3 || | 1 +---+|+|
-- | | | | | 2 || | +---++| | +---+|+ |
-- | | | | | || | | 2 | | | | 2 |+ |
-- +----------+---+ +---------+---++ +--------+---+-+ +------+---+---+
-- (1) (2) (3) (4)
-- +----------+---+ +----------+---+ +----------+---+ +----------+---+
-- | | | | | 3 | | | 4 | | +---+|
-- | | | -> | | | -> | +---++ -> | +---+|+
-- | 1 | 2 | | 1 +---++ | 1 | 3 || | 1 +---+|+|
-- | | | | | 2 || | +---++| | +---+|+ |
-- | | | | | || | | 2 | | | | 2 |+ |
-- +----------+---+ +---------+---++ +--------+---+-+ +------+---+---+
local mwfact
if cascade.tile.mwfact > 0 then
mwfact = cascade.tile.mwfact
else
mwfact = t.master_width_factor
end
local mwfact
if cascade.tile.mwfact > 0 then
mwfact = cascade.tile.mwfact
else
mwfact = t.master_width_factor
end
-- Make slave windows overlap main window? Do this if ncol is 1.
local overlap_main
if cascade.tile.ncol > 0 then
overlap_main = cascade.tile.ncol
else
overlap_main = t.column_count
end
-- Make slave windows overlap main window? Do this if ncol is 1.
local overlap_main
if cascade.tile.ncol > 0 then
overlap_main = cascade.tile.ncol
else
overlap_main = t.column_count
end
-- Minimum space for slave windows? See cascade.tile.lua.
local num_c
if cascade.tile.nmaster > 0 then
num_c = cascade.tile.nmaster
else
num_c = t.master_count
end
-- 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 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)
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
if #cls <= 0 then
return
end
-- Main column, fixed width and height.
local c = cls[1]
local g = {}
-- Rounding is necessary to prevent the rendered size of slavewid
-- from being 1 pixel off when the result is not an integer.
local mainwid = floor(wa.width * mwfact)
local slavewid = wa.width - mainwid
-- Main column, fixed width and height.
local c = cls[1]
local g = {}
-- Rounding is necessary to prevent the rendered size of slavewid
-- from being 1 pixel off when the result is not an integer.
local mainwid = floor(wa.width * mwfact)
local slavewid = wa.width - mainwid
if overlap_main == 1 then
g.width = wa.width
if overlap_main == 1 then
g.width = wa.width
-- The size of the main window may be reduced a little bit.
-- This allows you to see if there are any windows below the
-- main window.
-- This only makes sense, though, if the main window is
-- overlapping everything else.
g.width = g.width - cascade.tile.extra_padding
else
g.width = mainwid
end
-- The size of the main window may be reduced a little bit.
-- This allows you to see if there are any windows below the
-- main window.
-- This only makes sense, though, if the main window is
-- overlapping everything else.
g.width = g.width - cascade.tile.extra_padding
else
g.width = mainwid
end
g.height = wa.height
g.x = wa.x
g.y = wa.y
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.width < 1 then
g.width = 1
end
if g.height < 1 then
g.height = 1
end
p.geometries[c] = 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 = {}
-- 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.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
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
if g.width < 1 then
g.width = 1
end
if g.height < 1 then
g.height = 1
end
p.geometries[c] = g
end
end
p.geometries[c] = g
end
end
end
function cascade.tile.arrange(p)
return do_cascade(p, true)
return do_cascade(p, true)
end
function cascade.arrange(p)
return do_cascade(p, false)
return do_cascade(p, false)
end
return cascade

View File

@ -17,260 +17,274 @@ local mousegrabber = mousegrabber
local screen = screen
local centerwork = {
name = "centerwork",
horizontal = { name = "centerworkh" }
name = 'centerwork',
horizontal = { name = 'centerworkh' },
}
local function arrange(p, layout)
local t = p.tag or screen[p.screen].selected_tag
local wa = p.workarea
local cls = p.clients
local t = p.tag or screen[p.screen].selected_tag
local wa = p.workarea
local cls = p.clients
if #cls == 0 then return end
if #cls == 0 then
return
end
local g = {}
local g = {}
-- Main column, fixed width and height
local mwfact = t.master_width_factor
local mainhei = floor(wa.height * mwfact)
local mainwid = floor(wa.width * mwfact)
local slavewid = wa.width - mainwid
local slaveLwid = floor(slavewid / 2)
local slaveRwid = slavewid - slaveLwid
local slavehei = wa.height - mainhei
local slaveThei = floor(slavehei / 2)
local slaveBhei = slavehei - slaveThei
local nbrFirstSlaves = floor(#cls / 2)
local nbrSecondSlaves = floor((#cls - 1) / 2)
-- Main column, fixed width and height
local mwfact = t.master_width_factor
local mainhei = floor(wa.height * mwfact)
local mainwid = floor(wa.width * mwfact)
local slavewid = wa.width - mainwid
local slaveLwid = floor(slavewid / 2)
local slaveRwid = slavewid - slaveLwid
local slavehei = wa.height - mainhei
local slaveThei = floor(slavehei / 2)
local slaveBhei = slavehei - slaveThei
local nbrFirstSlaves = floor(#cls / 2)
local nbrSecondSlaves = floor((#cls - 1) / 2)
local slaveFirstDim, slaveSecondDim = 0, 0
local slaveFirstDim, slaveSecondDim = 0, 0
if layout.name == "centerwork" then -- vertical
if nbrFirstSlaves > 0 then slaveFirstDim = floor(wa.height / nbrFirstSlaves) end
if nbrSecondSlaves > 0 then slaveSecondDim = floor(wa.height / nbrSecondSlaves) end
if layout.name == 'centerwork' then -- vertical
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
g.width = mainwid
g.x = wa.x + slaveLwid
g.y = wa.y
else -- horizontal
if nbrFirstSlaves > 0 then slaveFirstDim = floor(wa.width / nbrFirstSlaves) end
if nbrSecondSlaves > 0 then slaveSecondDim = floor(wa.width / nbrSecondSlaves) end
g.x = wa.x + slaveLwid
g.y = wa.y
else -- horizontal
if nbrFirstSlaves > 0 then
slaveFirstDim = floor(wa.width / nbrFirstSlaves)
end
if nbrSecondSlaves > 0 then
slaveSecondDim = floor(wa.width / nbrSecondSlaves)
end
g.height = mainhei
g.width = wa.width
g.height = mainhei
g.width = wa.width
g.x = wa.x
g.y = wa.y + slaveThei
end
g.x = wa.x
g.y = wa.y + slaveThei
end
g.width = max(g.width, 1)
g.height = max(g.height, 1)
g.width = max(g.width, 1)
g.height = max(g.height, 1)
p.geometries[cls[1]] = g
p.geometries[cls[1]] = g
-- Auxiliary clients
if #cls <= 1 then return end
for i = 2, #cls do
g = {}
local idxChecker, dimToAssign
-- Auxiliary clients
if #cls <= 1 then
return
end
for i = 2, #cls do
g = {}
local idxChecker, dimToAssign
local rowIndex = floor(i/2)
local rowIndex = floor(i / 2)
if layout.name == "centerwork" then
if i % 2 == 0 then -- left slave
g.x = wa.x
g.y = wa.y + (rowIndex - 1) * slaveFirstDim
g.width = slaveLwid
if layout.name == 'centerwork' then
if i % 2 == 0 then -- left slave
g.x = wa.x
g.y = wa.y + (rowIndex - 1) * slaveFirstDim
g.width = slaveLwid
idxChecker, dimToAssign = nbrFirstSlaves, slaveFirstDim
else -- right slave
g.x = wa.x + slaveLwid + mainwid
g.y = wa.y + (rowIndex - 1) * slaveSecondDim
g.width = slaveRwid
idxChecker, dimToAssign = nbrFirstSlaves, slaveFirstDim
else -- right slave
g.x = wa.x + slaveLwid + mainwid
g.y = wa.y + (rowIndex - 1) * slaveSecondDim
g.width = slaveRwid
idxChecker, dimToAssign = nbrSecondSlaves, slaveSecondDim
end
idxChecker, dimToAssign = nbrSecondSlaves, slaveSecondDim
end
-- if last slave in row, use remaining space for it
if rowIndex == idxChecker then
g.height = wa.y + wa.height - g.y
else
g.height = dimToAssign
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 row, use remaining space for it
if rowIndex == idxChecker then
g.height = wa.y + wa.height - g.y
else
g.height = dimToAssign
end
else
if i % 2 == 0 then -- top slave
g.x = wa.x + (rowIndex - 1) * slaveFirstDim
g.y = wa.y
g.height = slaveThei
idxChecker, dimToAssign = nbrFirstSlaves, slaveFirstDim
else -- bottom slave
g.x = wa.x + (rowIndex - 1) * slaveSecondDim
g.y = wa.y + slaveThei + mainhei
g.height = slaveBhei
idxChecker, dimToAssign = nbrFirstSlaves, slaveFirstDim
else -- bottom slave
g.x = wa.x + (rowIndex - 1) * slaveSecondDim
g.y = wa.y + slaveThei + mainhei
g.height = slaveBhei
idxChecker, dimToAssign = nbrSecondSlaves, slaveSecondDim
end
idxChecker, dimToAssign = nbrSecondSlaves, slaveSecondDim
end
-- if last slave in row, use remaining space for it
if rowIndex == idxChecker then
g.width = wa.x + wa.width - g.x
else
g.width = dimToAssign
end
end
-- if last slave in row, use remaining space for it
if rowIndex == idxChecker then
g.width = wa.x + wa.width - g.x
else
g.width = dimToAssign
end
end
g.width = max(g.width, 1)
g.height = max(g.height, 1)
g.width = max(g.width, 1)
g.height = max(g.height, 1)
p.geometries[cls[i]] = g
end
p.geometries[cls[i]] = g
end
end
local function mouse_resize_handler(c, _, _, _, orientation)
local wa = c.screen.workarea
local mwfact = c.screen.selected_tag.master_width_factor
local g = c:geometry()
local offset = 0
local cursor = "cross"
local wa = c.screen.workarea
local mwfact = c.screen.selected_tag.master_width_factor
local g = c:geometry()
local offset = 0
local cursor = 'cross'
local corner_coords
local corner_coords
if orientation == 'vertical' then
if g.height + 15 >= wa.height then
offset = g.height * .5
cursor = "sb_h_double_arrow"
elseif g.y + g.height + 15 <= wa.y + wa.height then
offset = g.height
end
corner_coords = { x = wa.x + wa.width * (1 - mwfact) / 2, y = g.y + offset }
else
if g.width + 15 >= wa.width then
offset = g.width * .5
cursor = "sb_v_double_arrow"
elseif g.x + g.width + 15 <= wa.x + wa.width then
offset = g.width
end
corner_coords = { y = wa.y + wa.height * (1 - mwfact) / 2, x = g.x + offset }
end
if orientation == 'vertical' then
if g.height + 15 >= wa.height then
offset = g.height * 0.5
cursor = 'sb_h_double_arrow'
elseif g.y + g.height + 15 <= wa.y + wa.height then
offset = g.height
end
corner_coords = { x = wa.x + wa.width * (1 - mwfact) / 2, y = g.y + offset }
else
if g.width + 15 >= wa.width then
offset = g.width * 0.5
cursor = 'sb_v_double_arrow'
elseif g.x + g.width + 15 <= wa.x + wa.width then
offset = g.width
end
corner_coords = { y = wa.y + wa.height * (1 - mwfact) / 2, x = g.x + offset }
end
mouse.coords(corner_coords)
mouse.coords(corner_coords)
local prev_coords = {}
local prev_coords = {}
mousegrabber.run(function(m)
if not c.valid then return false end
for _, v in ipairs(m.buttons) do
if v then
prev_coords = { x = m.x, y = m.y }
local new_mwfact
if orientation == 'vertical' then
new_mwfact = 1 - (m.x - wa.x) / wa.width * 2
else
new_mwfact = 1 - (m.y - wa.y) / wa.height * 2
end
c.screen.selected_tag.master_width_factor = math.min(math.max(new_mwfact, 0.01), 0.99)
return true
end
end
return prev_coords.x == m.x and prev_coords.y == m.y
end, cursor)
mousegrabber.run(function(m)
if not c.valid then
return false
end
for _, v in ipairs(m.buttons) do
if v then
prev_coords = { x = m.x, y = m.y }
local new_mwfact
if orientation == 'vertical' then
new_mwfact = 1 - (m.x - wa.x) / wa.width * 2
else
new_mwfact = 1 - (m.y - wa.y) / wa.height * 2
end
c.screen.selected_tag.master_width_factor = math.min(math.max(new_mwfact, 0.01), 0.99)
return true
end
end
return prev_coords.x == m.x and prev_coords.y == m.y
end, cursor)
end
function centerwork.arrange(p)
return arrange(p, centerwork)
return arrange(p, centerwork)
end
function centerwork.horizontal.arrange(p)
return arrange(p, centerwork.horizontal)
return arrange(p, centerwork.horizontal)
end
function centerwork.mouse_resize_handler(c, corner, x, y)
return mouse_resize_handler(c, corner, x, y, 'vertical')
return mouse_resize_handler(c, corner, x, y, 'vertical')
end
function centerwork.horizontal.mouse_resize_handler(c, corner, x, y)
return mouse_resize_handler(c, corner, x, y, 'horizontal')
return mouse_resize_handler(c, corner, x, y, 'horizontal')
end
--[[
Make focus.byidx and swap.byidx behave more consistently with other layouts.
--]]
local awful = require("awful")
local gears = require("gears")
local awful = require('awful')
local gears = require('gears')
local client = client
local function compare_position(a, b)
if a.x == b.x then
return a.y < b.y
else
return a.x < b.x
end
if a.x == b.x then
return a.y < b.y
else
return a.x < b.x
end
end
local function clients_by_position()
local this = client.focus
if this then
local sorted = {}
for _, c in ipairs(client.focus.first_tag:clients()) do
if not c.minimized then sorted[#sorted+1] = c end
end
table.sort(sorted, compare_position)
local this = client.focus
if this then
local sorted = {}
for _, c in ipairs(client.focus.first_tag:clients()) do
if not c.minimized then
sorted[#sorted + 1] = c
end
end
table.sort(sorted, compare_position)
local idx = 0
for i, that in ipairs(sorted) do
if this.window == that.window then
idx = i
end
end
local idx = 0
for i, that in ipairs(sorted) do
if this.window == that.window then
idx = i
end
end
if idx > 0 then
return { sorted = sorted, idx = idx }
end
end
return {}
if idx > 0 then
return { sorted = sorted, idx = idx }
end
end
return {}
end
local function in_centerwork()
return client.focus and client.focus.first_tag.layout.name == "centerwork"
return client.focus and client.focus.first_tag.layout.name == 'centerwork'
end
centerwork.focus = {}
--[[
Drop in replacements for awful.client.focus.byidx and awful.client.swap.byidx
that behaves consistently with other layouts.
--]]
function centerwork.focus.byidx(i)
if in_centerwork() then
local cls = clients_by_position()
if cls.idx then
local target = cls.sorted[gears.math.cycle(#cls.sorted, cls.idx + i)]
awful.client.focus.byidx(0, target)
end
else
awful.client.focus.byidx(i)
end
if in_centerwork() then
local cls = clients_by_position()
if cls.idx then
local target = cls.sorted[gears.math.cycle(#cls.sorted, cls.idx + i)]
awful.client.focus.byidx(0, target)
end
else
awful.client.focus.byidx(i)
end
end
centerwork.swap = {}
function centerwork.swap.byidx(i)
if in_centerwork() then
local cls = clients_by_position()
if cls.idx then
local target = cls.sorted[gears.math.cycle(#cls.sorted, cls.idx + i)]
client.focus:swap(target)
end
else
awful.client.swap.byidx(i)
end
if in_centerwork() then
local cls = clients_by_position()
if cls.idx then
local target = cls.sorted[gears.math.cycle(#cls.sorted, cls.idx + i)]
client.focus:swap(target)
end
else
awful.client.swap.byidx(i)
end
end
return centerwork

View File

@ -11,9 +11,9 @@
--]]
local wrequire = require("lain.helpers").wrequire
local wrequire = require('lain.helpers').wrequire
local setmetatable = setmetatable
local layout = { _NAME = "lain.layout" }
local layout = { _NAME = 'lain.layout' }
return setmetatable(layout, { __index = wrequire })

View File

@ -8,275 +8,305 @@
--]]
local math = math
local screen = screen
local math = math
local screen = screen
local tonumber = tonumber
local termfair = { name = "termfair" }
termfair.center = { name = "centerfair" }
termfair.stable = { name = "stablefair" }
local termfair = { name = 'termfair' }
termfair.center = { name = 'centerfair' }
termfair.stable = { name = 'stablefair' }
local function do_fair(p, orientation)
local t = p.tag or screen[p.screen].selected_tag
local wa = p.workarea
local cls = p.clients
local t = p.tag or screen[p.screen].selected_tag
local wa = p.workarea
local cls = p.clients
if #cls == 0 then return end
if #cls == 0 then
return
end
-- 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)
-- 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)
if orientation == "west" then
-- Layout with fixed number of vertical columns (read from nmaster).
-- New windows align from left to right. When a row is full, a new
-- one above it is created. Like this:
if orientation == 'west' then
-- Layout with fixed number of vertical columns (read from nmaster).
-- New windows align from left to right. When a row is full, a new
-- one above it is created. Like this:
-- (1) (2) (3)
-- +---+---+---+ +---+---+---+ +---+---+---+
-- | | | | | | | | | | | |
-- | 1 | | | -> | 1 | 2 | | -> | 1 | 2 | 3 | ->
-- | | | | | | | | | | | |
-- +---+---+---+ +---+---+---+ +---+---+---+
-- (1) (2) (3)
-- +---+---+---+ +---+---+---+ +---+---+---+
-- | | | | | | | | | | | |
-- | 1 | | | -> | 1 | 2 | | -> | 1 | 2 | 3 | ->
-- | | | | | | | | | | | |
-- +---+---+---+ +---+---+---+ +---+---+---+
-- (4) (5) (6)
-- +---+---+---+ +---+---+---+ +---+---+---+
-- | 1 | | | | 1 | 2 | | | 1 | 2 | 3 |
-- +---+---+---+ -> +---+---+---+ -> +---+---+---+
-- | 2 | 3 | 4 | | 3 | 4 | 5 | | 4 | 5 | 6 |
-- +---+---+---+ +---+---+---+ +---+---+---+
-- (4) (5) (6)
-- +---+---+---+ +---+---+---+ +---+---+---+
-- | 1 | | | | 1 | 2 | | | 1 | 2 | 3 |
-- +---+---+---+ -> +---+---+---+ -> +---+---+---+
-- | 2 | 3 | 4 | | 3 | 4 | 5 | | 4 | 5 | 6 |
-- +---+---+---+ +---+---+---+ +---+---+---+
local num_y = math.max(math.ceil(#cls / num_x), ncol)
local height = math.floor(wa.height/num_y)
local cur_num_x = num_x
local at_x = 0
local at_y = 0
local num_y = math.max(math.ceil(#cls / num_x), ncol)
local height = math.floor(wa.height / num_y)
local cur_num_x = num_x
local at_x = 0
local at_y = 0
local remaining_clients = #cls
local remaining_clients = #cls
-- We start the first row. Left-align by limiting the number of
-- available slots.
if remaining_clients < num_x then
cur_num_x = remaining_clients
end
-- We start the first row. Left-align by limiting the number of
-- available slots.
if remaining_clients < num_x then
cur_num_x = remaining_clients
end
-- Iterate in reversed order.
for i = #cls,1,-1 do
-- Get x and y position.
local c = cls[i]
local this_x = cur_num_x - at_x - 1
local this_y = num_y - at_y - 1
-- Iterate in reversed order.
for i = #cls, 1, -1 do
-- Get x and y position.
local c = cls[i]
local this_x = cur_num_x - at_x - 1
local this_y = num_y - at_y - 1
-- Calculate geometry.
local g = {}
if this_x == (num_x - 1) then
g.width = wa.width - (num_x - 1)*width
else
g.width = width
end
-- Calculate geometry.
local g = {}
if this_x == (num_x - 1) then
g.width = wa.width - (num_x - 1) * width
else
g.width = width
end
if this_y == (num_y - 1) then
g.height = wa.height - (num_y - 1)*height
else
g.height = height
end
if this_y == (num_y - 1) then
g.height = wa.height - (num_y - 1) * height
else
g.height = height
end
g.x = wa.x + this_x*width
g.y = wa.y + this_y*height
g.x = wa.x + this_x * width
g.y = wa.y + this_y * height
if g.width < 1 then g.width = 1 end
if g.height < 1 then g.height = 1 end
if g.width < 1 then
g.width = 1
end
if g.height < 1 then
g.height = 1
end
p.geometries[c] = g
p.geometries[c] = g
remaining_clients = remaining_clients - 1
remaining_clients = remaining_clients - 1
-- Next grid position.
at_x = at_x + 1
if at_x == num_x then
-- Row full, create a new one above it.
at_x = 0
at_y = at_y + 1
-- Next grid position.
at_x = at_x + 1
if at_x == num_x then
-- Row full, create a new one above it.
at_x = 0
at_y = at_y + 1
-- We start a new row. Left-align.
if remaining_clients < num_x then
cur_num_x = remaining_clients
end
end
end
elseif orientation == "stable" then
-- Layout with fixed number of vertical columns (read from nmaster).
-- New windows align from left to right. When a row is full, a new
-- one below it is created. Like this:
-- We start a new row. Left-align.
if remaining_clients < num_x then
cur_num_x = remaining_clients
end
end
end
elseif orientation == 'stable' then
-- Layout with fixed number of vertical columns (read from nmaster).
-- New windows align from left to right. When a row is full, a new
-- one below it is created. Like this:
-- (1) (2) (3)
-- +---+---+---+ +---+---+---+ +---+---+---+
-- | | | | | | | | | | | |
-- | 1 | | | -> | 1 | 2 | | -> | 1 | 2 | 3 | ->
-- | | | | | | | | | | | |
-- +---+---+---+ +---+---+---+ +---+---+---+
-- (1) (2) (3)
-- +---+---+---+ +---+---+---+ +---+---+---+
-- | | | | | | | | | | | |
-- | 1 | | | -> | 1 | 2 | | -> | 1 | 2 | 3 | ->
-- | | | | | | | | | | | |
-- +---+---+---+ +---+---+---+ +---+---+---+
-- (4) (5) (6)
-- +---+---+---+ +---+---+---+ +---+---+---+
-- | 1 | 2 | 3 | | 1 | 2 | 3 | | 1 | 2 | 3 |
-- +---+---+---+ +---+---+---+ +---+---+---+
-- | 4 | | | | 4 | 5 | | | 4 | 5 | 6 |
-- +---+---+---+ -> +---+---+---+ -> +---+---+---+
-- (4) (5) (6)
-- +---+---+---+ +---+---+---+ +---+---+---+
-- | 1 | 2 | 3 | | 1 | 2 | 3 | | 1 | 2 | 3 |
-- +---+---+---+ +---+---+---+ +---+---+---+
-- | 4 | | | | 4 | 5 | | | 4 | 5 | 6 |
-- +---+---+---+ -> +---+---+---+ -> +---+---+---+
local num_y = math.max(math.ceil(#cls / num_x), ncol)
local height = math.floor(wa.height/num_y)
local num_y = math.max(math.ceil(#cls / num_x), ncol)
local height = math.floor(wa.height / num_y)
for i = #cls,1,-1 do
-- Get x and y position.
local c = cls[i]
local this_x = (i - 1) % num_x
local this_y = math.floor((i - this_x - 1) / num_x)
for i = #cls, 1, -1 do
-- Get x and y position.
local c = cls[i]
local this_x = (i - 1) % num_x
local this_y = math.floor((i - this_x - 1) / num_x)
-- Calculate geometry.
local g = {}
if this_x == (num_x - 1) then
g.width = wa.width - (num_x - 1)*width
else
g.width = width
end
-- Calculate geometry.
local g = {}
if this_x == (num_x - 1) then
g.width = wa.width - (num_x - 1) * width
else
g.width = width
end
if this_y == (num_y - 1) then
g.height = wa.height - (num_y - 1)*height
else
g.height = height
end
if this_y == (num_y - 1) then
g.height = wa.height - (num_y - 1) * height
else
g.height = height
end
g.x = wa.x + this_x*width
g.y = wa.y + this_y*height
g.x = wa.x + this_x * width
g.y = wa.y + this_y * height
if g.width < 1 then g.width = 1 end
if g.height < 1 then g.height = 1 end
if g.width < 1 then
g.width = 1
end
if g.height < 1 then
g.height = 1
end
p.geometries[c] = g
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.
p.geometries[c] = g
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 | ->
-- | | | | | | | | | | | | |
-- +---+---+---+ +-+---+---+-+ +---+---+---+
-- 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 |
-- +---+---+---+ +---+---+---+
-- (4) (5)
-- +---+---+---+ +---+---+---+
-- | | | 3 | | | 2 | 4 |
-- + 1 + 2 +---+ -> + 1 +---+---+
-- | | | 4 | | | 3 | 5 |
-- +---+---+---+ +---+---+---+
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
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
-- 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))
-- 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
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 _ = 0, (num_y[i]-2) do
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
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
-- 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 _ = 0, (num_y[i] - 2) do
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
g = {}
g.x = wx
g.y = wy
g.height = wa.height - (num_y[i] - 1) * height
g.width = width
if g.width < 1 then
g.width = 1
end
if g.height < 1 then
g.height = 1
end
p.geometries[cls[nclient]] = g
nclient = nclient + 1
wx = wx + width
end
end
end
end
function termfair.center.arrange(p)
return do_fair(p, "center")
return do_fair(p, 'center')
end
function termfair.stable.arrange(p)
return do_fair(p, "stable")
return do_fair(p, 'stable')
end
function termfair.arrange(p)
return do_fair(p, "west")
return do_fair(p, 'west')
end
return termfair

File diff suppressed because it is too large Load Diff

View File

@ -11,180 +11,200 @@
--]]
local awful = require("awful")
local sqrt = math.sqrt
local pairs = pairs
local client = client
local tonumber = tonumber
local wrequire = require("lain.helpers").wrequire
local awful = require('awful')
local sqrt = math.sqrt
local pairs = pairs
local client = client
local tonumber = tonumber
local wrequire = require('lain.helpers').wrequire
local setmetatable = setmetatable
-- Lain utilities submodule
-- lain.util
local util = { _NAME = "lain.util" }
local util = { _NAME = 'lain.util' }
-- Like awful.menu.clients, but only show clients of currently selected tags
function util.menu_clients_current_tags(menu, args)
-- List of currently selected tags.
local cls_tags = awful.screen.focused().selected_tags
-- List of currently selected tags.
local cls_tags = awful.screen.focused().selected_tags
if cls_tags == nil then return nil end
if cls_tags == nil then
return nil
end
-- Final list of menu items.
local cls_t = {}
-- Final list of menu items.
local cls_t = {}
-- For each selected tag get all clients of that tag and add them to
-- the menu. A click on a menu item will raise that client.
for i = 1,#cls_tags do
local t = cls_tags[i]
local cls = t:clients()
-- For each selected tag get all clients of that tag and add them to
-- the menu. A click on a menu item will raise that client.
for i = 1, #cls_tags do
local t = cls_tags[i]
local cls = t:clients()
for _, c in pairs(cls) do
cls_t[#cls_t + 1] = { awful.util.escape(c.name) or "",
function ()
c.minimized = false
client.focus = c
c:raise()
end,
c.icon }
end
end
for _, c in pairs(cls) do
cls_t[#cls_t + 1] = {
awful.util.escape(c.name) or '',
function()
c.minimized = false
client.focus = c
c:raise()
end,
c.icon,
}
end
end
-- No clients? Then quit.
if #cls_t <= 0 then return nil end
-- No clients? Then quit.
if #cls_t <= 0 then
return nil
end
-- menu may contain some predefined values, otherwise start with a
-- fresh menu.
if not menu then menu = {} end
-- menu may contain some predefined values, otherwise start with a
-- fresh menu.
if not menu then
menu = {}
end
-- Set the list of items and show the menu.
menu.items = cls_t
local m = awful.menu(menu)
m:show(args)
-- Set the list of items and show the menu.
menu.items = cls_t
local m = awful.menu(menu)
m:show(args)
return m
return m
end
-- Magnify a client: set it to "float" and resize it.
function util.magnify_client(c, width_f, height_f)
if c and not c.floating then
util.magnified_client = c
util.mc(c, width_f, height_f)
else
util.magnified_client = nil
c.floating = false
end
if c and not c.floating then
util.magnified_client = c
util.mc(c, width_f, height_f)
else
util.magnified_client = nil
c.floating = false
end
end
-- https://github.com/lcpz/lain/issues/195
function util.mc(c, width_f, height_f)
c = c or util.magnified_client
if not c then return end
c = c or util.magnified_client
if not c then
return
end
c.floating = true
local s = awful.screen.focused()
local mg = s.workarea
local g = {}
local mwfact = width_f or s.selected_tag.master_width_factor or 0.5
g.width = sqrt(mwfact) * mg.width
g.height = sqrt(height_f or mwfact) * mg.height
g.x = mg.x + (mg.width - g.width) / 2
g.y = mg.y + (mg.height - g.height) / 2
c.floating = true
local s = awful.screen.focused()
local mg = s.workarea
local g = {}
local mwfact = width_f or s.selected_tag.master_width_factor or 0.5
g.width = sqrt(mwfact) * mg.width
g.height = sqrt(height_f or mwfact) * mg.height
g.x = mg.x + (mg.width - g.width) / 2
g.y = mg.y + (mg.height - g.height) / 2
if c then c:geometry(g) end -- if c is still a valid object
if c then
c:geometry(g)
end -- if c is still a valid object
end
-- Non-empty tag browsing
-- direction in {-1, 1} <-> {previous, next} non-empty tag
function util.tag_view_nonempty(direction,sc)
direction = direction or 1
local s = sc or awful.screen.focused()
local tags = s.tags
local sel = s.selected_tag
function util.tag_view_nonempty(direction, sc)
direction = direction or 1
local s = sc or awful.screen.focused()
local tags = s.tags
local sel = s.selected_tag
local i = sel.index
repeat
i = i + direction
local i = sel.index
repeat
i = i + direction
-- Wrap around when we reach one of the bounds
if i > #tags then
i = i - #tags
end
if i < 1 then
i = i + #tags
end
-- Wrap around when we reach one of the bounds
if i > #tags then
i = i - #tags
end
if i < 1 then
i = i + #tags
end
local t = tags[i]
local t = tags[i]
-- Stop when we get back to where we started
if t == sel then
break
end
-- Stop when we get back to where we started
if t == sel then
break
end
-- If it's The One, view it.
if #t:clients() > 0 then
t:view_only()
return
end
until false
-- If it's The One, view it.
if #t:clients() > 0 then
t:view_only()
return
end
until false
end
-- {{{ Dynamic tagging
-- Add a new tag
function util.add_tag(layout)
awful.prompt.run {
prompt = "New tag name: ",
textbox = awful.screen.focused().mypromptbox.widget,
exe_callback = function(name)
if not name or #name == 0 then return end
awful.tag.add(name, { screen = awful.screen.focused(), layout = layout or awful.layout.suit.tile }):view_only()
end
}
awful.prompt.run({
prompt = 'New tag name: ',
textbox = awful.screen.focused().mypromptbox.widget,
exe_callback = function(name)
if not name or #name == 0 then
return
end
awful.tag
.add(name, { screen = awful.screen.focused(), layout = layout or awful.layout.suit.tile })
:view_only()
end,
})
end
-- Rename current tag
function util.rename_tag()
awful.prompt.run {
prompt = "Rename tag: ",
textbox = awful.screen.focused().mypromptbox.widget,
exe_callback = function(new_name)
if not new_name or #new_name == 0 then return end
local t = awful.screen.focused().selected_tag
if t then
t.name = new_name
end
end
}
awful.prompt.run({
prompt = 'Rename tag: ',
textbox = awful.screen.focused().mypromptbox.widget,
exe_callback = function(new_name)
if not new_name or #new_name == 0 then
return
end
local t = awful.screen.focused().selected_tag
if t then
t.name = new_name
end
end,
})
end
-- Move current tag
-- pos in {-1, 1} <-> {previous, next} tag position
function util.move_tag(pos)
local tag = awful.screen.focused().selected_tag
if tonumber(pos) <= -1 then
awful.tag.move(tag.index - 1, tag)
else
awful.tag.move(tag.index + 1, tag)
end
local tag = awful.screen.focused().selected_tag
if tonumber(pos) <= -1 then
awful.tag.move(tag.index - 1, tag)
else
awful.tag.move(tag.index + 1, tag)
end
end
-- Delete current tag
-- Any rule set on the tag shall be broken
function util.delete_tag()
local t = awful.screen.focused().selected_tag
if not t then return end
t:delete()
local t = awful.screen.focused().selected_tag
if not t then
return
end
t:delete()
end
-- }}}
-- On the fly useless gaps change
function util.useless_gaps_resize(thatmuch, s, t)
local scr = s or awful.screen.focused()
local tag = t or scr.selected_tag
tag.gap = tag.gap + tonumber(thatmuch)
awful.layout.arrange(scr)
local scr = s or awful.screen.focused()
local tag = t or scr.selected_tag
tag.gap = tag.gap + tonumber(thatmuch)
awful.layout.arrange(scr)
end
return setmetatable(util, { __index = wrequire })

View File

@ -15,52 +15,78 @@ local setmetatable = setmetatable
local markup = { fg = {}, bg = {} }
-- Convenience tags
function markup.bold(text) return format("<b>%s</b>", text) end
function markup.italic(text) return format("<i>%s</i>", text) end
function markup.strike(text) return format("<s>%s</s>", text) end
function markup.underline(text) return format("<u>%s</u>", text) end
function markup.monospace(text) return format("<tt>%s</tt>", text) end
function markup.big(text) return format("<big>%s</big>", text) end
function markup.small(text) return format("<small>%s</small>", text) end
function markup.bold(text)
return format('<b>%s</b>', text)
end
function markup.italic(text)
return format('<i>%s</i>', text)
end
function markup.strike(text)
return format('<s>%s</s>', text)
end
function markup.underline(text)
return format('<u>%s</u>', text)
end
function markup.monospace(text)
return format('<tt>%s</tt>', text)
end
function markup.big(text)
return format('<big>%s</big>', text)
end
function markup.small(text)
return format('<small>%s</small>', text)
end
-- Set the font
function markup.font(font, text)
return format("<span font='%s'>%s</span>", font, text)
return format("<span font='%s'>%s</span>", font, text)
end
-- Set the foreground
function markup.fg.color(color, text)
return format("<span foreground='%s'>%s</span>", color, text)
return format("<span foreground='%s'>%s</span>", color, text)
end
-- Set the background
function markup.bg.color(color, text)
return format("<span background='%s'>%s</span>", color, text)
return format("<span background='%s'>%s</span>", color, text)
end
-- Set foreground and background
function markup.color(fg, bg, text)
return format("<span foreground='%s' background='%s'>%s</span>", fg, bg, text)
return format("<span foreground='%s' background='%s'>%s</span>", fg, bg, text)
end
-- Set font and foreground
function markup.fontfg(font, fg, text)
return format("<span font='%s' foreground='%s'>%s</span>", font, fg, text)
return format("<span font='%s' foreground='%s'>%s</span>", font, fg, text)
end
-- Set font and background
function markup.fontbg(font, bg, text)
return format("<span font='%s' background='%s'>%s</span>", font, bg, text)
return format("<span font='%s' background='%s'>%s</span>", font, bg, text)
end
-- Set font, foreground and background
function markup.fontcolor(font, fg, bg, text)
return format("<span font='%s' foreground='%s' background='%s'>%s</span>", font, fg, bg, text)
return format("<span font='%s' foreground='%s' background='%s'>%s</span>", font, fg, bg, text)
end
-- link markup.{fg,bg}(...) calls to markup.{fg,bg}.color(...)
setmetatable(markup.fg, { __call = function(_, ...) return markup.fg.color(...) end })
setmetatable(markup.bg, { __call = function(_, ...) return markup.bg.color(...) end })
setmetatable(markup.fg, {
__call = function(_, ...)
return markup.fg.color(...)
end,
})
setmetatable(markup.bg, {
__call = function(_, ...)
return markup.bg.color(...)
end,
})
-- link markup(...) calls to markup.fg.color(...)
return setmetatable(markup, { __call = function(_, ...) return markup.fg.color(...) end })
return setmetatable(markup, {
__call = function(_, ...)
return markup.fg.color(...)
end,
})

View File

@ -10,28 +10,30 @@
-- Menu iterator with Naughty notifications
-- lain.util.menu_iterator
local naughty = require("naughty")
local helpers = require("lain.helpers")
local atable = require("awful.util").table
local assert = assert
local pairs = pairs
local naughty = require('naughty')
local helpers = require('lain.helpers')
local atable = require('awful.util').table
local assert = assert
local pairs = pairs
local tconcat = table.concat
local unpack = unpack or table.unpack -- lua 5.1 retro-compatibility
local state = { cid = nil }
local function naughty_destroy_callback(reason)
local closed = naughty.notificationClosedReason
if reason == closed.expired or reason == closed.dismissedByUser then
local actions = state.index and state.menu[state.index - 1][2]
if actions then
for _,action in pairs(actions) do
-- don't try to call nil callbacks
if action then action() end
end
state.index = nil
end
end
local closed = naughty.notificationClosedReason
if reason == closed.expired or reason == closed.dismissedByUser then
local actions = state.index and state.menu[state.index - 1][2]
if actions then
for _, action in pairs(actions) do
-- don't try to call nil callbacks
if action then
action()
end
end
state.index = nil
end
end
end
-- Iterates over a menu.
@ -41,35 +43,35 @@ end
-- * timeout: time to wait before confirming the menu selection
-- * icon: icon to display in the notification of the chosen label
local function iterate(menu, timeout, icon)
timeout = timeout or 4 -- default timeout for each menu entry
icon = icon or nil -- icon to display on the menu
timeout = timeout or 4 -- default timeout for each menu entry
icon = icon or nil -- icon to display on the menu
-- Build the list of choices
if not state.index then
state.menu = menu
state.index = 1
end
-- Build the list of choices
if not state.index then
state.menu = menu
state.index = 1
end
-- Select one and display the appropriate notification
local label
local next = state.menu[state.index]
state.index = state.index + 1
-- Select one and display the appropriate notification
local label
local next = state.menu[state.index]
state.index = state.index + 1
if not next then
label = "Cancel"
state.index = nil
else
label, _ = unpack(next)
end
if not next then
label = 'Cancel'
state.index = nil
else
label, _ = unpack(next)
end
state.cid = naughty.notify({
text = label,
icon = icon,
timeout = timeout,
screen = mouse.screen,
replaces_id = state.cid,
destroy = naughty_destroy_callback
}).id
state.cid = naughty.notify({
text = label,
icon = icon,
timeout = timeout,
screen = mouse.screen,
replaces_id = state.cid,
destroy = naughty_destroy_callback,
}).id
end
-- Generates a menu compatible with the first argument of `iterate` function and
@ -95,50 +97,55 @@ end
-- Output:
-- * m: menu to be iterated over.
local function menu(args)
local choices = assert(args.choices or args[1])
local name = assert(args.name or args[2])
local selected_cb = args.selected_cb
local rejected_cb = args.rejected_cb
local extra_choices = args.extra_choices or {}
local choices = assert(args.choices or args[1])
local name = assert(args.name or args[2])
local selected_cb = args.selected_cb
local rejected_cb = args.rejected_cb
local extra_choices = args.extra_choices or {}
local ch_combinations = args.combination == "powerset" and helpers.powerset(choices) or helpers.trivial_partition_set(choices)
local ch_combinations = args.combination == 'powerset' and helpers.powerset(choices)
or helpers.trivial_partition_set(choices)
for _, c in pairs(extra_choices) do
ch_combinations = atable.join(ch_combinations, {{c[1]}})
end
for _, c in pairs(extra_choices) do
ch_combinations = atable.join(ch_combinations, { { c[1] } })
end
local m = {} -- the menu
local m = {} -- the menu
for _,c in pairs(ch_combinations) do
if #c > 0 then
local cbs = {}
for _, c in pairs(ch_combinations) do
if #c > 0 then
local cbs = {}
-- selected choices
for _,ch in pairs(c) do
if atable.hasitem(choices, ch) then
cbs[#cbs + 1] = selected_cb and function() selected_cb(ch) end or nil
end
end
-- selected choices
for _, ch in pairs(c) do
if atable.hasitem(choices, ch) then
cbs[#cbs + 1] = selected_cb and function()
selected_cb(ch)
end or nil
end
end
-- rejected choices
for _,ch in pairs(choices) do
if not atable.hasitem(c, ch) and atable.hasitem(choices, ch) then
cbs[#cbs + 1] = rejected_cb and function() rejected_cb(ch) end or nil
end
end
-- rejected choices
for _, ch in pairs(choices) do
if not atable.hasitem(c, ch) and atable.hasitem(choices, ch) then
cbs[#cbs + 1] = rejected_cb and function()
rejected_cb(ch)
end or nil
end
end
-- add user extra choices (like the choice "None" for example)
for _,x in pairs(extra_choices) do
if x[1] == c[1] then
cbs[#cbs + 1] = x[2]
end
end
-- add user extra choices (like the choice "None" for example)
for _, x in pairs(extra_choices) do
if x[1] == c[1] then
cbs[#cbs + 1] = x[2]
end
end
m[#m + 1] = { name .. ": " .. tconcat(c, " + "), cbs }
end
end
m[#m + 1] = { name .. ': ' .. tconcat(c, ' + '), cbs }
end
end
return m
return m
end
return { iterate = iterate, menu = menu }

View File

@ -6,12 +6,12 @@
--]]
local awful = require("awful")
local capi = { client = client }
local math = math
local string = string
local pairs = pairs
local screen = screen
local awful = require('awful')
local capi = { client = client }
local math = math
local string = string
local pairs = pairs
local screen = screen
local setmetatable = setmetatable
-- Quake-like Dropdown application spawn
@ -22,158 +22,182 @@ local quake = {}
-- run into problems with focus.
function quake:display()
if self.followtag then self.screen = awful.screen.focused() end
if self.followtag then
self.screen = awful.screen.focused()
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)
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
-- 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)
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 and not self.visible then
return
end
if not client then
-- The client does not exist, we spawn it
local cmd = string.format("%s %s %s", self.app,
string.format(self.argname, self.name), self.extra)
awful.spawn(cmd, { tag = self.screen.selected_tag })
return
end
if not client then
-- The client does not exist, we spawn it
local cmd = string.format('%s %s %s', self.app, string.format(self.argname, self.name), self.extra)
awful.spawn(cmd, { tag = self.screen.selected_tag })
return
end
-- Set geometry
client.floating = true
client.border_width = self.border
client.size_hints_honor = false
local maximized = client.maximized
local fullscreen = client.fullscreen
client:geometry(self.geometry[self.screen.index] or self:compute_size())
-- Set geometry
client.floating = true
client.border_width = self.border
client.size_hints_honor = false
local maximized = client.maximized
local fullscreen = client.fullscreen
client:geometry(self.geometry[self.screen.index] or self:compute_size())
-- Set not sticky and on top
client.sticky = false
client.ontop = true
client.above = true
client.skip_taskbar = true
-- Set not sticky and on top
client.sticky = false
client.ontop = true
client.above = true
client.skip_taskbar = true
-- Additional user settings
if self.settings then self.settings(client) end
-- Additional user settings
if self.settings then
self.settings(client)
end
-- Toggle display
if self.visible then
client.hidden = false
client.maximized = self.maximized
client.fullscreen = self.fullscreen
client:raise()
self.last_tag = self.screen.selected_tag
client:tags({self.screen.selected_tag})
capi.client.focus = client
else
self.maximized = maximized
self.fullscreen = fullscreen
client.maximized = false
client.fullscreen = false
client.hidden = true
local ctags = client:tags()
for j, _ in pairs(ctags) do
ctags[j] = nil
end
client:tags(ctags)
end
-- Toggle display
if self.visible then
client.hidden = false
client.maximized = self.maximized
client.fullscreen = self.fullscreen
client:raise()
self.last_tag = self.screen.selected_tag
client:tags({ self.screen.selected_tag })
capi.client.focus = client
else
self.maximized = maximized
self.fullscreen = fullscreen
client.maximized = false
client.fullscreen = false
client.hidden = true
local ctags = client:tags()
for j, _ in pairs(ctags) do
ctags[j] = nil
end
client:tags(ctags)
end
return client
return client
end
function quake:compute_size()
-- skip if we already have a geometry for this screen
if not self.geometry[self.screen.index] then
local geom
if not self.overlap then
geom = screen[self.screen.index].workarea
else
geom = screen[self.screen.index].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.index] = { x = x, y = y, width = width, height = height }
end
return self.geometry[self.screen.index]
-- skip if we already have a geometry for this screen
if not self.geometry[self.screen.index] then
local geom
if not self.overlap then
geom = screen[self.screen.index].workarea
else
geom = screen[self.screen.index].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.index] = { x = x, y = y, width = width, height = height }
end
return self.geometry[self.screen.index]
end
function quake:toggle()
if self.followtag then self.screen = awful.screen.focused() end
local current_tag = self.screen.selected_tag
if current_tag and self.last_tag ~= current_tag and self.visible then
local c=self:display()
if c then
c:move_to_tag(current_tag)
end
else
self.visible = not self.visible
self:display()
end
if self.followtag then
self.screen = awful.screen.focused()
end
local current_tag = self.screen.selected_tag
if current_tag and self.last_tag ~= current_tag and self.visible then
local c = self:display()
if c then
c:move_to_tag(current_tag)
end
else
self.visible = not self.visible
self:display()
end
end
function quake.new(conf)
conf = conf or {}
conf = conf 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.border = conf.border or 1 -- client border width
conf.visible = conf.visible or false -- initially not visible
conf.followtag = conf.followtag or false -- spawn on currently focused screen
conf.overlap = conf.overlap or false -- overlap wibox
conf.screen = conf.screen or awful.screen.focused()
conf.settings = conf.settings
conf.app = conf.app or 'xterm' -- application to spawn
conf.name = conf.name or 'QuakeDD' -- window name
conf.argname = conf.argname or '-name %s' -- how to specify window name
conf.extra = conf.extra or '' -- extra arguments
conf.border = conf.border or 1 -- client border width
conf.visible = conf.visible or false -- initially not visible
conf.followtag = conf.followtag or false -- spawn on currently focused screen
conf.overlap = conf.overlap or false -- overlap wibox
conf.screen = conf.screen or awful.screen.focused()
conf.settings = conf.settings
-- If width or height <= 1 this is a proportion of the workspace
conf.height = conf.height or 0.25 -- height
conf.width = conf.width or 1 -- width
conf.vert = conf.vert or "top" -- top, bottom or center
conf.horiz = conf.horiz or "left" -- left, right or center
conf.geometry = {} -- internal use
-- If width or height <= 1 this is a proportion of the workspace
conf.height = conf.height or 0.25 -- height
conf.width = conf.width or 1 -- width
conf.vert = conf.vert or 'top' -- top, bottom or center
conf.horiz = conf.horiz or 'left' -- left, right or center
conf.geometry = {} -- internal use
conf.maximized = false
conf.fullscreen = false
conf.maximized = false
conf.fullscreen = false
local dropdown = setmetatable(conf, { __index = quake })
local dropdown = setmetatable(conf, { __index = quake })
capi.client.connect_signal("manage", function(c)
if c.instance == dropdown.name and c.screen == dropdown.screen then
dropdown:display()
end
end)
capi.client.connect_signal("unmanage", function(c)
if c.instance == dropdown.name and c.screen == dropdown.screen then
dropdown.visible = false
end
end)
capi.client.connect_signal('manage', function(c)
if c.instance == dropdown.name and c.screen == dropdown.screen then
dropdown:display()
end
end)
capi.client.connect_signal('unmanage', function(c)
if c.instance == dropdown.name and c.screen == dropdown.screen then
dropdown.visible = false
end
end)
return dropdown
return dropdown
end
return setmetatable(quake, { __call = function(_, ...) return quake.new(...) end })
return setmetatable(quake, {
__call = function(_, ...)
return quake.new(...)
end,
})

View File

@ -6,9 +6,9 @@
--]]
local wibox = require("wibox")
local gears = require("gears")
local beautiful = require("beautiful")
local wibox = require('wibox')
local gears = require('gears')
local beautiful = require('beautiful')
-- Lain Cairo separators util submodule
-- lain.util.separators
@ -18,99 +18,99 @@ local separators = { height = beautiful.separators_height or 0, width = beautifu
-- Right
function separators.arrow_right(col1, col2)
local widget = wibox.widget.base.make_widget()
widget.col1 = col1
widget.col2 = col2
local widget = wibox.widget.base.make_widget()
widget.col1 = col1
widget.col2 = col2
widget.fit = function(_, _, _)
return separators.width, separators.height
end
widget.fit = function(_, _, _)
return separators.width, separators.height
end
widget.update = function(_, _)
widget.col1 = col1
widget.col2 = col2
widget:emit_signal("widget::redraw_needed")
end
widget.update = function(_, _)
widget.col1 = col1
widget.col2 = col2
widget:emit_signal('widget::redraw_needed')
end
widget.draw = function(_, _, cr, width, height)
if widget.col2 ~= "alpha" then
cr:set_source_rgba(gears.color.parse_color(widget.col2))
cr:new_path()
cr:move_to(0, 0)
cr:line_to(width, height/2)
cr:line_to(width, 0)
cr:close_path()
cr:fill()
widget.draw = function(_, _, cr, width, height)
if widget.col2 ~= 'alpha' then
cr:set_source_rgba(gears.color.parse_color(widget.col2))
cr:new_path()
cr:move_to(0, 0)
cr:line_to(width, height / 2)
cr:line_to(width, 0)
cr:close_path()
cr:fill()
cr:new_path()
cr:move_to(0, height)
cr:line_to(width, height/2)
cr:line_to(width, height)
cr:close_path()
cr:fill()
end
cr:new_path()
cr:move_to(0, height)
cr:line_to(width, height / 2)
cr:line_to(width, height)
cr:close_path()
cr:fill()
end
if widget.col1 ~= "alpha" then
cr:set_source_rgba(gears.color.parse_color(widget.col1))
cr:new_path()
cr:move_to(0, 0)
cr:line_to(width, height/2)
cr:line_to(0, height)
cr:close_path()
cr:fill()
end
end
if widget.col1 ~= 'alpha' then
cr:set_source_rgba(gears.color.parse_color(widget.col1))
cr:new_path()
cr:move_to(0, 0)
cr:line_to(width, height / 2)
cr:line_to(0, height)
cr:close_path()
cr:fill()
end
end
return widget
return widget
end
-- Left
function separators.arrow_left(col1, col2)
local widget = wibox.widget.base.make_widget()
widget.col1 = col1
widget.col2 = col2
local widget = wibox.widget.base.make_widget()
widget.col1 = col1
widget.col2 = col2
widget.fit = function(_, _, _)
return separators.width, separators.height
end
widget.fit = function(_, _, _)
return separators.width, separators.height
end
widget.update = function(c1, c2)
widget.col1 = c1
widget.col2 = c2
widget:emit_signal("widget::redraw_needed")
end
widget.update = function(c1, c2)
widget.col1 = c1
widget.col2 = c2
widget:emit_signal('widget::redraw_needed')
end
widget.draw = function(_, _, cr, width, height)
if widget.col1 ~= "alpha" then
cr:set_source_rgba(gears.color.parse_color(widget.col1))
cr:new_path()
cr:move_to(width, 0)
cr:line_to(0, height/2)
cr:line_to(0, 0)
cr:close_path()
cr:fill()
widget.draw = function(_, _, cr, width, height)
if widget.col1 ~= 'alpha' then
cr:set_source_rgba(gears.color.parse_color(widget.col1))
cr:new_path()
cr:move_to(width, 0)
cr:line_to(0, height / 2)
cr:line_to(0, 0)
cr:close_path()
cr:fill()
cr:new_path()
cr:move_to(width, height)
cr:line_to(0, height/2)
cr:line_to(0, height)
cr:close_path()
cr:fill()
end
cr:new_path()
cr:move_to(width, height)
cr:line_to(0, height / 2)
cr:line_to(0, height)
cr:close_path()
cr:fill()
end
if widget.col2 ~= "alpha" then
cr:new_path()
cr:move_to(width, 0)
cr:line_to(0, height/2)
cr:line_to(width, height)
cr:close_path()
if widget.col2 ~= 'alpha' then
cr:new_path()
cr:move_to(width, 0)
cr:line_to(0, height / 2)
cr:line_to(width, height)
cr:close_path()
cr:set_source_rgba(gears.color.parse_color(widget.col2))
cr:fill()
end
end
cr:set_source_rgba(gears.color.parse_color(widget.col2))
cr:fill()
end
end
return widget
return widget
end
-- ]]

View File

@ -6,49 +6,52 @@
--]]
local helpers = require("lain.helpers")
local shell = require("awful.util").shell
local wibox = require("wibox")
local string = string
local helpers = require('lain.helpers')
local shell = require('awful.util').shell
local wibox = require('wibox')
local string = string
-- ALSA volume
-- lain.widget.alsa
local function factory(args)
args = args or {}
local alsa = { widget = args.widget or wibox.widget.textbox() }
local timeout = args.timeout or 5
local settings = args.settings or function() end
args = args or {}
local alsa = { widget = args.widget or wibox.widget.textbox() }
local timeout = args.timeout or 5
local settings = args.settings or function() end
alsa.cmd = args.cmd or "amixer"
alsa.channel = args.channel or "Master"
alsa.togglechannel = args.togglechannel
alsa.cmd = args.cmd or 'amixer'
alsa.channel = args.channel or 'Master'
alsa.togglechannel = args.togglechannel
local format_cmd = string.format("%s get %s", alsa.cmd, alsa.channel)
local format_cmd = string.format('%s get %s', alsa.cmd, alsa.channel)
if alsa.togglechannel then
format_cmd = { shell, "-c", string.format("%s get %s; %s get %s",
alsa.cmd, alsa.channel, alsa.cmd, alsa.togglechannel) }
end
if alsa.togglechannel then
format_cmd = {
shell,
'-c',
string.format('%s get %s; %s get %s', alsa.cmd, alsa.channel, alsa.cmd, alsa.togglechannel),
}
end
alsa.last = {}
alsa.last = {}
function alsa.update()
helpers.async(format_cmd, function(mixer)
local l,s = string.match(mixer, "([%d]+)%%.*%[([%l]*)")
l = tonumber(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
function alsa.update()
helpers.async(format_cmd, function(mixer)
local l, s = string.match(mixer, '([%d]+)%%.*%[([%l]*)')
l = tonumber(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)
helpers.newtimer(string.format('alsa-%s-%s', alsa.cmd, alsa.channel), timeout, alsa.update)
return alsa
return alsa
end
return factory

View File

@ -6,161 +6,167 @@
--]]
local helpers = require("lain.helpers")
local awful = require("awful")
local naughty = require("naughty")
local wibox = require("wibox")
local math = math
local string = string
local type = type
local helpers = require('lain.helpers')
local awful = require('awful')
local naughty = require('naughty')
local wibox = require('wibox')
local math = math
local string = string
local type = type
local tonumber = tonumber
-- ALSA volume bar
-- lain.widget.alsabar
local function factory(args)
local alsabar = {
colors = {
background = "#000000",
mute = "#EB8F8F",
unmute = "#A4CE8A"
},
local alsabar = {
colors = {
background = '#000000',
mute = '#EB8F8F',
unmute = '#A4CE8A',
},
_current_level = 0,
_playback = "off"
}
_current_level = 0,
_playback = 'off',
}
args = args or {}
args = args or {}
local timeout = args.timeout or 5
local settings = args.settings or function() end
local width = args.width or 63
local height = args.height or 1
local margins = args.margins or 1
local ticks = args.ticks or false
local ticks_size = args.ticks_size or 7
local tick = args.tick or "|"
local tick_pre = args.tick_pre or "["
local tick_post = args.tick_post or "]"
local tick_none = args.tick_none or " "
local timeout = args.timeout or 5
local settings = args.settings or function() end
local width = args.width or 63
local height = args.height or 1
local margins = args.margins or 1
local ticks = args.ticks or false
local ticks_size = args.ticks_size or 7
local tick = args.tick or '|'
local tick_pre = args.tick_pre or '['
local tick_post = args.tick_post or ']'
local tick_none = args.tick_none or ' '
alsabar.cmd = args.cmd or "amixer"
alsabar.channel = args.channel or "Master"
alsabar.togglechannel = args.togglechannel
alsabar.colors = args.colors or alsabar.colors
alsabar.followtag = args.followtag or false
alsabar.notification_preset = args.notification_preset
alsabar.cmd = args.cmd or 'amixer'
alsabar.channel = args.channel or 'Master'
alsabar.togglechannel = args.togglechannel
alsabar.colors = args.colors or alsabar.colors
alsabar.followtag = args.followtag or false
alsabar.notification_preset = args.notification_preset
if not alsabar.notification_preset then
alsabar.notification_preset = { font = "Monospace 10" }
end
if not alsabar.notification_preset then
alsabar.notification_preset = { font = 'Monospace 10' }
end
local format_cmd = string.format("%s get %s", alsabar.cmd, alsabar.channel)
local format_cmd = string.format('%s get %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
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
alsabar.bar = wibox.widget {
color = alsabar.colors.unmute,
background_color = alsabar.colors.background,
forced_height = height,
forced_width = width,
margins = margins,
paddings = margins,
ticks = ticks,
ticks_size = ticks_size,
widget = wibox.widget.progressbar
}
alsabar.bar = wibox.widget({
color = alsabar.colors.unmute,
background_color = alsabar.colors.background,
forced_height = height,
forced_width = width,
margins = margins,
paddings = margins,
ticks = ticks,
ticks_size = ticks_size,
widget = wibox.widget.progressbar,
})
alsabar.tooltip = awful.tooltip({ objects = { alsabar.bar } })
alsabar.tooltip = awful.tooltip({ objects = { alsabar.bar } })
function alsabar.update(callback)
helpers.async(format_cmd, function(mixer)
local vol, playback = string.match(mixer, "([%d]+)%%.*%[([%l]*)")
function alsabar.update(callback)
helpers.async(format_cmd, function(mixer)
local vol, playback = string.match(mixer, '([%d]+)%%.*%[([%l]*)')
if not vol or not playback then return end
if not vol or not playback then
return
end
if vol ~= alsabar._current_level or playback ~= alsabar._playback then
alsabar._current_level = tonumber(vol)
alsabar.bar:set_value(alsabar._current_level / 100)
if alsabar._current_level == 0 or playback == "off" then
alsabar._playback = playback
alsabar.tooltip:set_text("[Muted]")
alsabar.bar.color = alsabar.colors.mute
else
alsabar._playback = "on"
alsabar.tooltip:set_text(string.format("%s: %s", alsabar.channel, vol))
alsabar.bar.color = alsabar.colors.unmute
end
if vol ~= alsabar._current_level or playback ~= alsabar._playback then
alsabar._current_level = tonumber(vol)
alsabar.bar:set_value(alsabar._current_level / 100)
if alsabar._current_level == 0 or playback == 'off' then
alsabar._playback = playback
alsabar.tooltip:set_text('[Muted]')
alsabar.bar.color = alsabar.colors.mute
else
alsabar._playback = 'on'
alsabar.tooltip:set_text(string.format('%s: %s', alsabar.channel, vol))
alsabar.bar.color = alsabar.colors.unmute
end
volume_now = {
level = alsabar._current_level,
status = alsabar._playback
}
volume_now = {
level = alsabar._current_level,
status = alsabar._playback,
}
settings()
settings()
if type(callback) == "function" then callback() end
end
end)
end
if type(callback) == 'function' then
callback()
end
end
end)
end
function alsabar.notify()
alsabar.update(function()
local preset = alsabar.notification_preset
function alsabar.notify()
alsabar.update(function()
local preset = alsabar.notification_preset
preset.title = string.format("%s - %s%%", alsabar.channel, alsabar._current_level)
preset.title = string.format('%s - %s%%', alsabar.channel, alsabar._current_level)
if alsabar._playback == "off" then
preset.title = preset.title .. " Muted"
end
if alsabar._playback == 'off' then
preset.title = preset.title .. ' Muted'
end
-- tot is the maximum number of ticks to display in the notification
local tot = alsabar.notification_preset.max_ticks
-- tot is the maximum number of ticks to display in the notification
local tot = alsabar.notification_preset.max_ticks
if not tot then
local wib = awful.screen.focused().mywibox
-- if we can grab mywibox, tot is defined as its height if
-- horizontal, or width otherwise
if wib then
if wib.position == "left" or wib.position == "right" then
tot = wib.width
else
tot = wib.height
end
-- fallback: default horizontal wibox height
else
tot = 20
end
end
if not tot then
local wib = awful.screen.focused().mywibox
-- if we can grab mywibox, tot is defined as its height if
-- horizontal, or width otherwise
if wib then
if wib.position == 'left' or wib.position == 'right' then
tot = wib.width
else
tot = wib.height
end
-- fallback: default horizontal wibox height
else
tot = 20
end
end
local int = math.modf((alsabar._current_level / 100) * tot)
preset.text = string.format(
"%s%s%s%s",
tick_pre,
string.rep(tick, int),
string.rep(tick_none, tot - int),
tick_post
)
local int = math.modf((alsabar._current_level / 100) * tot)
preset.text =
string.format('%s%s%s%s', tick_pre, string.rep(tick, int), string.rep(tick_none, tot - int), tick_post)
if alsabar.followtag then preset.screen = awful.screen.focused() end
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
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)
helpers.newtimer(string.format('alsabar-%s-%s', alsabar.cmd, alsabar.channel), timeout, alsabar.update)
return alsabar
return alsabar
end
return factory

View File

@ -6,231 +6,232 @@
--]]
local helpers = require("lain.helpers")
local fs = require("gears.filesystem")
local naughty = require("naughty")
local wibox = require("wibox")
local math = math
local string = string
local ipairs = ipairs
local helpers = require('lain.helpers')
local fs = require('gears.filesystem')
local naughty = require('naughty')
local wibox = require('wibox')
local math = math
local string = string
local ipairs = ipairs
local tonumber = tonumber
-- Battery infos
-- lain.widget.bat
local function factory(args)
local pspath = args.pspath or "/sys/class/power_supply/"
local pspath = args.pspath or '/sys/class/power_supply/'
if not fs.is_dir(pspath) then
naughty.notify { text = "lain.widget.bat: invalid power supply path", timeout = 0 }
return
end
if not fs.is_dir(pspath) then
naughty.notify({ text = 'lain.widget.bat: invalid power supply path', timeout = 0 })
return
end
args = args or {}
args = args or {}
local bat = { widget = args.widget or wibox.widget.textbox() }
local timeout = args.timeout or 30
local notify = args.notify or "on"
local full_notify = args.full_notify or notify
local n_perc = args.n_perc or { 5, 15 }
local batteries = args.batteries or (args.battery and {args.battery}) or {}
local ac = args.ac or "AC0"
local settings = args.settings or function() end
local bat = { widget = args.widget or wibox.widget.textbox() }
local timeout = args.timeout or 30
local notify = args.notify or 'on'
local full_notify = args.full_notify or notify
local n_perc = args.n_perc or { 5, 15 }
local batteries = args.batteries or (args.battery and { args.battery }) or {}
local ac = args.ac or 'AC0'
local settings = args.settings or function() end
function bat.get_batteries()
helpers.line_callback("ls -1 " .. pspath, function(line)
local bstr = string.match(line, "BAT%w+")
if bstr then
batteries[#batteries + 1] = bstr
else
ac = string.match(line, "A%w+") or ac
end
end)
end
function bat.get_batteries()
helpers.line_callback('ls -1 ' .. pspath, function(line)
local bstr = string.match(line, 'BAT%w+')
if bstr then
batteries[#batteries + 1] = bstr
else
ac = string.match(line, 'A%w+') or ac
end
end)
end
if #batteries == 0 then bat.get_batteries() end
if #batteries == 0 then
bat.get_batteries()
end
bat_notification_critical_preset = {
title = "Battery exhausted",
text = "Shutdown imminent",
timeout = 15,
fg = "#000000",
bg = "#FFFFFF"
}
bat_notification_critical_preset = {
title = 'Battery exhausted',
text = 'Shutdown imminent',
timeout = 15,
fg = '#000000',
bg = '#FFFFFF',
}
bat_notification_low_preset = {
title = "Battery low",
text = "Plug the cable!",
timeout = 15,
fg = "#202020",
bg = "#CDCDCD"
}
bat_notification_low_preset = {
title = 'Battery low',
text = 'Plug the cable!',
timeout = 15,
fg = '#202020',
bg = '#CDCDCD',
}
bat_notification_charged_preset = {
title = "Battery full",
text = "You can unplug the cable",
timeout = 15,
fg = "#202020",
bg = "#CDCDCD"
}
bat_notification_charged_preset = {
title = 'Battery full',
text = 'You can unplug the cable',
timeout = 15,
fg = '#202020',
bg = '#CDCDCD',
}
bat_now = {
status = "N/A",
ac_status = "N/A",
perc = "N/A",
time = "N/A",
watt = "N/A",
capacity = "N/A"
}
bat_now = {
status = 'N/A',
ac_status = 'N/A',
perc = 'N/A',
time = 'N/A',
watt = 'N/A',
capacity = 'N/A',
}
bat_now.n_status = {}
bat_now.n_perc = {}
bat_now.n_capacity = {}
for i = 1, #batteries do
bat_now.n_status[i] = "N/A"
bat_now.n_perc[i] = 0
bat_now.n_capacity[i] = 0
end
bat_now.n_status = {}
bat_now.n_perc = {}
bat_now.n_capacity = {}
for i = 1, #batteries do
bat_now.n_status[i] = 'N/A'
bat_now.n_perc[i] = 0
bat_now.n_capacity[i] = 0
end
-- used to notify full charge only once before discharging
local fullnotification = false
-- used to notify full charge only once before discharging
local fullnotification = false
function bat.update()
-- luacheck: globals bat_now
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_charge_full = 0
local sum_charge_design = 0
function bat.update()
-- luacheck: globals bat_now
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_charge_full = 0
local sum_charge_design = 0
for i, battery in ipairs(batteries) do
local bstr = pspath .. battery
local present = helpers.first_line(bstr .. "/present")
for i, battery in ipairs(batteries) do
local bstr = pspath .. battery
local present = helpers.first_line(bstr .. '/present')
if tonumber(present) == 1 then
-- current_now(I)[uA], voltage_now(U)[uV], power_now(P)[uW]
local rate_current = tonumber(helpers.first_line(bstr .. "/current_now"))
local rate_voltage = tonumber(helpers.first_line(bstr .. "/voltage_now"))
local rate_power = tonumber(helpers.first_line(bstr .. "/power_now"))
local charge_full = tonumber(helpers.first_line(bstr .. "/charge_full"))
local charge_design = tonumber(helpers.first_line(bstr .. "/charge_full_design"))
if tonumber(present) == 1 then
-- current_now(I)[uA], voltage_now(U)[uV], power_now(P)[uW]
local rate_current = tonumber(helpers.first_line(bstr .. '/current_now'))
local rate_voltage = tonumber(helpers.first_line(bstr .. '/voltage_now'))
local rate_power = tonumber(helpers.first_line(bstr .. '/power_now'))
local charge_full = tonumber(helpers.first_line(bstr .. '/charge_full'))
local charge_design = tonumber(helpers.first_line(bstr .. '/charge_full_design'))
-- energy_now(P)[uWh], charge_now(I)[uAh]
local energy_now = tonumber(helpers.first_line(bstr .. "/energy_now") or
helpers.first_line(bstr .. "/charge_now"))
-- energy_now(P)[uWh], charge_now(I)[uAh]
local energy_now =
tonumber(helpers.first_line(bstr .. '/energy_now') or helpers.first_line(bstr .. '/charge_now'))
-- energy_full(P)[uWh], charge_full(I)[uAh]
local energy_full = tonumber(helpers.first_line(bstr .. "/energy_full") or
charge_full)
-- energy_full(P)[uWh], charge_full(I)[uAh]
local energy_full = tonumber(helpers.first_line(bstr .. '/energy_full') or charge_full)
local energy_percentage = tonumber(helpers.first_line(bstr .. "/capacity")) or
math.floor((energy_now / energy_full) * 100)
local energy_percentage = tonumber(helpers.first_line(bstr .. '/capacity'))
or math.floor((energy_now / energy_full) * 100)
bat_now.n_status[i] = helpers.first_line(bstr .. "/status") or "N/A"
bat_now.n_perc[i] = energy_percentage or bat_now.n_perc[i]
bat_now.n_status[i] = helpers.first_line(bstr .. '/status') or 'N/A'
bat_now.n_perc[i] = energy_percentage or bat_now.n_perc[i]
if not charge_design or charge_design == 0 then
bat_now.n_capacity[i] = 0
else
bat_now.n_capacity[i] = math.floor((charge_full / charge_design) * 100)
end
if not charge_design or charge_design == 0 then
bat_now.n_capacity[i] = 0
else
bat_now.n_capacity[i] = math.floor((charge_full / charge_design) * 100)
end
sum_rate_current = sum_rate_current + (rate_current or 0)
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)
sum_charge_full = sum_charge_full + (charge_full or 0)
sum_charge_design = sum_charge_design + (charge_design or 0)
end
end
sum_rate_current = sum_rate_current + (rate_current or 0)
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)
sum_charge_full = sum_charge_full + (charge_full or 0)
sum_charge_design = sum_charge_design + (charge_design or 0)
end
end
bat_now.capacity = math.floor(math.min(100, (sum_charge_full / sum_charge_design) * 100))
bat_now.capacity = math.floor(math.min(100, (sum_charge_full / sum_charge_design) * 100))
-- 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] or "N/A"
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(helpers.first_line(string.format("%s%s/online", pspath, ac))) or "N/A"
-- 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] or 'N/A'
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(helpers.first_line(string.format('%s%s/online', pspath, ac))) or 'N/A'
if bat_now.status ~= "N/A" then
if bat_now.status ~= "Full" and sum_rate_power == 0 and bat_now.ac_status == 1 then
bat_now.perc = math.floor(math.min(100, (sum_energy_now / sum_energy_full) * 100))
bat_now.time = "00:00"
bat_now.watt = 0
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
elseif bat_now.status ~= "Full" then
local rate_time = 0
-- 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
-- update {perc,time,watt} iff battery not full and rate > 0
elseif bat_now.status ~= 'Full' then
local rate_time = 0
-- Calculate time and watt if rates are greater then 0
if sum_rate_power > 0 or sum_rate_current > 0 then
local div = (sum_rate_power > 0 and sum_rate_power) or sum_rate_current
if bat_now.status == "Charging" then
rate_time = (sum_energy_full - sum_energy_now) / div
else -- Discharging
rate_time = sum_energy_now / div
end
if bat_now.status == 'Charging' then
rate_time = (sum_energy_full - sum_energy_now) / div
else -- Discharging
rate_time = sum_energy_now / div
end
if 0 < rate_time and rate_time < 0.01 then -- check for magnitude discrepancies (#199)
rate_time_magnitude = math.abs(math.floor(math.log10(rate_time)))
rate_time = rate_time * 10^(rate_time_magnitude - 2)
end
end
if 0 < rate_time and rate_time < 0.01 then -- check for magnitude discrepancies (#199)
rate_time_magnitude = math.abs(math.floor(math.log10(rate_time)))
rate_time = rate_time * 10 ^ (rate_time_magnitude - 2)
end
end
local hours = math.floor(rate_time)
local minutes = math.floor((rate_time - hours) * 60)
bat_now.perc = math.floor(math.min(100, (sum_energy_now / sum_energy_full) * 100))
bat_now.time = string.format("%02d:%02d", hours, minutes)
bat_now.watt = tonumber(string.format("%.2f", sum_rate_energy / 1e6))
elseif bat_now.status == "Full" then
bat_now.perc = 100
bat_now.time = "00:00"
bat_now.watt = 0
end
end
local hours = math.floor(rate_time)
local minutes = math.floor((rate_time - hours) * 60)
bat_now.perc = math.floor(math.min(100, (sum_energy_now / sum_energy_full) * 100))
bat_now.time = string.format('%02d:%02d', hours, minutes)
bat_now.watt = tonumber(string.format('%.2f', sum_rate_energy / 1e6))
elseif bat_now.status == 'Full' then
bat_now.perc = 100
bat_now.time = '00:00'
bat_now.watt = 0
end
end
widget = bat.widget
settings()
widget = bat.widget
settings()
-- notifications for critical, low, and full levels
if notify == "on" then
if bat_now.status == "Discharging" then
if tonumber(bat_now.perc) <= n_perc[1] then
bat.id = naughty.notify({
preset = bat_notification_critical_preset,
replaces_id = bat.id
}).id
elseif tonumber(bat_now.perc) <= n_perc[2] then
bat.id = naughty.notify({
preset = bat_notification_low_preset,
replaces_id = bat.id
}).id
end
fullnotification = false
elseif bat_now.status == "Full" and full_notify == "on" and not fullnotification then
bat.id = naughty.notify({
preset = bat_notification_charged_preset,
replaces_id = bat.id
}).id
fullnotification = true
end
end
end
-- notifications for critical, low, and full levels
if notify == 'on' then
if bat_now.status == 'Discharging' then
if tonumber(bat_now.perc) <= n_perc[1] then
bat.id = naughty.notify({
preset = bat_notification_critical_preset,
replaces_id = bat.id,
}).id
elseif tonumber(bat_now.perc) <= n_perc[2] then
bat.id = naughty.notify({
preset = bat_notification_low_preset,
replaces_id = bat.id,
}).id
end
fullnotification = false
elseif bat_now.status == 'Full' and full_notify == 'on' and not fullnotification then
bat.id = naughty.notify({
preset = bat_notification_charged_preset,
replaces_id = bat.id,
}).id
fullnotification = true
end
end
end
helpers.newtimer("batteries", timeout, bat.update)
helpers.newtimer('batteries', timeout, bat.update)
return bat
return bat
end
return factory

View File

@ -5,16 +5,16 @@
--]]
local helpers = require("lain.helpers")
local markup = require("lain.util.markup")
local awful = require("awful")
local naughty = require("naughty")
local floor = math.floor
local os = os
local pairs = pairs
local string = string
local tconcat = table.concat
local type = type
local helpers = require('lain.helpers')
local markup = require('lain.util.markup')
local awful = require('awful')
local naughty = require('naughty')
local floor = math.floor
local os = os
local pairs = pairs
local string = string
local tconcat = table.concat
local type = type
local tonumber = tonumber
local tostring = tostring
@ -22,170 +22,205 @@ local tostring = tostring
-- lain.widget.cal
local function factory(args)
args = args or {}
local cal = {
attach_to = args.attach_to or {},
week_start = args.week_start or 2,
three = args.three or false,
followtag = args.followtag or false,
week_number = args.week_number or "none",
week_number_format = args.week_number_format or args.week_number == "left" and "%3d | " or "| %-3d",
icons = args.icons or helpers.icons_dir .. "cal/white/",
notification_preset = args.notification_preset or {
font = "Monospace 10", fg = "#FFFFFF", bg = "#000000"
}
}
args = args or {}
local cal = {
attach_to = args.attach_to or {},
week_start = args.week_start or 2,
three = args.three or false,
followtag = args.followtag or false,
week_number = args.week_number or 'none',
week_number_format = args.week_number_format or args.week_number == 'left' and '%3d | ' or '| %-3d',
icons = args.icons or helpers.icons_dir .. 'cal/white/',
notification_preset = args.notification_preset or {
font = 'Monospace 10',
fg = '#FFFFFF',
bg = '#000000',
},
}
function cal.get_week_number(m, st_day, x)
local date = os.date("*t", m)
function cal.get_week_number(m, st_day, x)
local date = os.date('*t', m)
local week_step = (x ~= 0 and floor((x + st_day) / 7) - 1 or 0);
local week_step = (x ~= 0 and floor((x + st_day) / 7) - 1 or 0)
local display_time = os.time {
year = date.year, month = date.month, day = date.day + 7 * week_step
}
local display_time = os.time({
year = date.year,
month = date.month,
day = date.day + 7 * week_step,
})
return string.format(cal.week_number_format, os.date("%V", display_time))
end
return string.format(cal.week_number_format, os.date('%V', display_time))
end
function cal.sum_week_days(x, y)
return (x + y) % 7
end
function cal.sum_week_days(x, y)
return (x + y) % 7
end
function cal.build(month, year)
local current_month, current_year = tonumber(os.date("%m")), tonumber(os.date("%Y"))
local is_current_month = (not month or not year) or (month == current_month and year == current_year)
local today = is_current_month and tonumber(os.date("%d")) -- otherwise nil and not highlighted
local t = os.time { year = year or current_year, month = month and month+1 or current_month+1, day = 0 }
local d = os.date("*t", t)
local mth_days, st_day, this_month = d.day, (d.wday-d.day-cal.week_start+1)%7, os.date("%B %Y", t)
local notifytable = { [1] = string.format("%s%s\n", string.rep(" ", floor((28 - this_month:len())/2)), markup.bold(this_month)) }
for day_num = 0, 6 do
notifytable[#notifytable+1] = string.format("%3s ", os.date("%a", os.time { year = 2006, month = 1, day = day_num + cal.week_start }))
end
notifytable[#notifytable] = string.format("%s\n%s", notifytable[#notifytable]:sub(1, -2), string.rep(" ", st_day*4))
local strx
for x = 1,mth_days do
strx = x
if x == today then
if x < 10 then x = " " .. x end
strx = markup.bold(markup.color(cal.notification_preset.bg, cal.notification_preset.fg, x) .. " ")
end
strx = string.format("%s%s", string.rep(" ", 3 - tostring(x):len()), strx)
notifytable[#notifytable+1] = string.format("%-4s%s", strx, (x+st_day)%7==0 and x ~= mth_days and "\n" or "")
end
if string.len(cal.icons or "") > 0 and today then cal.icon = cal.icons .. today .. ".png" end
cal.month, cal.year = d.month, d.year
function cal.build(month, year)
local current_month, current_year = tonumber(os.date('%m')), tonumber(os.date('%Y'))
local is_current_month = (not month or not year) or (month == current_month and year == current_year)
local today = is_current_month and tonumber(os.date('%d')) -- otherwise nil and not highlighted
local t = os.time({ year = year or current_year, month = month and month + 1 or current_month + 1, day = 0 })
local d = os.date('*t', t)
local mth_days, st_day, this_month = d.day, (d.wday - d.day - cal.week_start + 1) % 7, os.date('%B %Y', t)
local notifytable = {
[1] = string.format('%s%s\n', string.rep(' ', floor((28 - this_month:len()) / 2)), markup.bold(this_month)),
}
for day_num = 0, 6 do
notifytable[#notifytable + 1] = string.format(
'%3s ',
os.date('%a', os.time({ year = 2006, month = 1, day = day_num + cal.week_start }))
)
end
notifytable[#notifytable] =
string.format('%s\n%s', notifytable[#notifytable]:sub(1, -2), string.rep(' ', st_day * 4))
local strx
for x = 1, mth_days do
strx = x
if x == today then
if x < 10 then
x = ' ' .. x
end
strx = markup.bold(markup.color(cal.notification_preset.bg, cal.notification_preset.fg, x) .. ' ')
end
strx = string.format('%s%s', string.rep(' ', 3 - tostring(x):len()), strx)
notifytable[#notifytable + 1] =
string.format('%-4s%s', strx, (x + st_day) % 7 == 0 and x ~= mth_days and '\n' or '')
end
if string.len(cal.icons or '') > 0 and today then
cal.icon = cal.icons .. today .. '.png'
end
cal.month, cal.year = d.month, d.year
if cal.week_number ~= "none" then
local m = os.time { year = year or current_year, month = month and month or current_month, day = 1 }
local head_prepend = string.rep(" ", tostring(string.format(cal.week_number_format, 0)):len())
if cal.week_number ~= 'none' then
local m = os.time({ year = year or current_year, month = month and month or current_month, day = 1 })
local head_prepend = string.rep(' ', tostring(string.format(cal.week_number_format, 0)):len())
if cal.week_number == "left" then
notifytable[1] = head_prepend .. notifytable[1] -- month-year row
notifytable[2] = head_prepend .. notifytable[2] -- weekdays row
notifytable[8] = notifytable[8]:gsub("\n", "\n" .. cal.get_week_number(m, st_day, 0)) -- first week of the month
if cal.week_number == 'left' then
notifytable[1] = head_prepend .. notifytable[1] -- month-year row
notifytable[2] = head_prepend .. notifytable[2] -- weekdays row
notifytable[8] = notifytable[8]:gsub('\n', '\n' .. cal.get_week_number(m, st_day, 0)) -- first week of the month
for x = 10,#notifytable do
if cal.sum_week_days(st_day, x) == 2 then
notifytable[x] = cal.get_week_number(m, st_day, x) .. notifytable[x]
end
end
elseif cal.week_number == "right" then
notifytable[8] = notifytable[8]:gsub("\n", head_prepend .. "\n") -- weekdays row
for x = 9,#notifytable do
if cal.sum_week_days(st_day, x) == 1 then
notifytable[x] = notifytable[x]:gsub("\n", cal.get_week_number(m, st_day, x - 7) .. "\n")
end
end
-- last week of the month
local end_days = cal.sum_week_days(st_day, mth_days)
if end_days ~= 0 then end_days = 7 - end_days end
notifytable[#notifytable] = notifytable[#notifytable] .. string.rep(" ", 4 * end_days) .. cal.get_week_number(m, st_day, mth_days + end_days)
end
end
for x = 10, #notifytable do
if cal.sum_week_days(st_day, x) == 2 then
notifytable[x] = cal.get_week_number(m, st_day, x) .. notifytable[x]
end
end
elseif cal.week_number == 'right' then
notifytable[8] = notifytable[8]:gsub('\n', head_prepend .. '\n') -- weekdays row
for x = 9, #notifytable do
if cal.sum_week_days(st_day, x) == 1 then
notifytable[x] = notifytable[x]:gsub('\n', cal.get_week_number(m, st_day, x - 7) .. '\n')
end
end
-- last week of the month
local end_days = cal.sum_week_days(st_day, mth_days)
if end_days ~= 0 then
end_days = 7 - end_days
end
notifytable[#notifytable] = notifytable[#notifytable]
.. string.rep(' ', 4 * end_days)
.. cal.get_week_number(m, st_day, mth_days + end_days)
end
end
return notifytable
end
return notifytable
end
function cal.getdate(month, year, offset)
if not month or not year then
month = tonumber(os.date("%m"))
year = tonumber(os.date("%Y"))
end
function cal.getdate(month, year, offset)
if not month or not year then
month = tonumber(os.date('%m'))
year = tonumber(os.date('%Y'))
end
month = month + offset
month = month + offset
while month > 12 do
month = month - 12
year = year + 1
end
while month > 12 do
month = month - 12
year = year + 1
end
while month < 1 do
month = month + 12
year = year - 1
end
while month < 1 do
month = month + 12
year = year - 1
end
return month, year
end
return month, year
end
function cal.hide()
if not cal.notification then return end
naughty.destroy(cal.notification)
cal.notification = nil
end
function cal.hide()
if not cal.notification then
return
end
naughty.destroy(cal.notification)
cal.notification = nil
end
function cal.show(seconds, month, year, scr)
local text = tconcat(cal.build(month, year))
function cal.show(seconds, month, year, scr)
local text = tconcat(cal.build(month, year))
if cal.three then
local current_month, current_year = cal.month, cal.year
local prev_month, prev_year = cal.getdate(cal.month, cal.year, -1)
local next_month, next_year = cal.getdate(cal.month, cal.year, 1)
text = string.format("%s\n\n%s\n\n%s",
tconcat(cal.build(prev_month, prev_year)), text,
tconcat(cal.build(next_month, next_year)))
cal.month, cal.year = current_month, current_year
end
if cal.three then
local current_month, current_year = cal.month, cal.year
local prev_month, prev_year = cal.getdate(cal.month, cal.year, -1)
local next_month, next_year = cal.getdate(cal.month, cal.year, 1)
text = string.format(
'%s\n\n%s\n\n%s',
tconcat(cal.build(prev_month, prev_year)),
text,
tconcat(cal.build(next_month, next_year))
)
cal.month, cal.year = current_month, current_year
end
if cal.notification then
local title = cal.notification_preset.title or nil
naughty.replace_text(cal.notification, title, text)
return
end
if cal.notification then
local title = cal.notification_preset.title or nil
naughty.replace_text(cal.notification, title, text)
return
end
cal.notification = naughty.notify {
preset = cal.notification_preset,
screen = cal.followtag and awful.screen.focused() or scr or 1,
icon = cal.icon,
timeout = type(seconds) == "number" and seconds or cal.notification_preset.timeout or 5,
text = text
}
end
cal.notification = naughty.notify({
preset = cal.notification_preset,
screen = cal.followtag and awful.screen.focused() or scr or 1,
icon = cal.icon,
timeout = type(seconds) == 'number' and seconds or cal.notification_preset.timeout or 5,
text = text,
})
end
function cal.hover_on() cal.show(0) end
function cal.move(offset)
offset = offset or 0
cal.month, cal.year = cal.getdate(cal.month, cal.year, offset)
cal.show(0, cal.month, cal.year)
end
function cal.prev() cal.move(-1) end
function cal.next() cal.move( 1) end
function cal.hover_on()
cal.show(0)
end
function cal.move(offset)
offset = offset or 0
cal.month, cal.year = cal.getdate(cal.month, cal.year, offset)
cal.show(0, cal.month, cal.year)
end
function cal.prev()
cal.move(-1)
end
function cal.next()
cal.move(1)
end
function cal.attach(widget)
widget:connect_signal("mouse::enter", cal.hover_on)
widget:connect_signal("mouse::leave", cal.hide)
widget:buttons(awful.util.table.join(
awful.button({}, 1, cal.prev),
awful.button({}, 3, cal.next),
awful.button({}, 2, cal.hover_on),
awful.button({}, 5, cal.prev),
awful.button({}, 4, cal.next)))
end
function cal.attach(widget)
widget:connect_signal('mouse::enter', cal.hover_on)
widget:connect_signal('mouse::leave', cal.hide)
widget:buttons(
awful.util.table.join(
awful.button({}, 1, cal.prev),
awful.button({}, 3, cal.next),
awful.button({}, 2, cal.hover_on),
awful.button({}, 5, cal.prev),
awful.button({}, 4, cal.next)
)
)
end
for _, widget in pairs(cal.attach_to) do cal.attach(widget) end
for _, widget in pairs(cal.attach_to) do
cal.attach(widget)
end
return cal
return cal
end
return factory

View File

@ -10,9 +10,9 @@
--]]
local wrequire = require("lain.helpers").wrequire
local wrequire = require('lain.helpers').wrequire
local setmetatable = setmetatable
local widget = { _NAME = "lain.widget.contrib" }
local widget = { _NAME = 'lain.widget.contrib' }
return setmetatable(widget, { __index = wrequire })

View File

@ -5,93 +5,103 @@
--]]
local helpers = require("lain.helpers")
local shell = require("awful.util").shell
local focused = require("awful.screen").focused
local escape_f = require("awful.util").escape
local naughty = require("naughty")
local wibox = require("wibox")
local os = os
local string = string
local helpers = require('lain.helpers')
local shell = require('awful.util').shell
local focused = require('awful.screen').focused
local escape_f = require('awful.util').escape
local naughty = require('naughty')
local wibox = require('wibox')
local os = os
local string = string
-- MOC audio player
-- lain.widget.contrib.moc
local function factory(args)
args = args or {}
args = args or {}
local moc = { widget = args.widget or wibox.widget.textbox() }
local timeout = args.timeout or 2
local music_dir = args.music_dir or os.getenv("HOME") .. "/Music"
local cover_pattern = args.cover_pattern or "*\\.(jpg|jpeg|png|gif)$"
local cover_size = args.cover_size or 100
local default_art = args.default_art or ""
local followtag = args.followtag or false
local settings = args.settings or function() end
local moc = { widget = args.widget or wibox.widget.textbox() }
local timeout = args.timeout or 2
local music_dir = args.music_dir or os.getenv('HOME') .. '/Music'
local cover_pattern = args.cover_pattern or '*\\.(jpg|jpeg|png|gif)$'
local cover_size = args.cover_size or 100
local default_art = args.default_art or ''
local followtag = args.followtag or false
local settings = args.settings or function() end
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()
helpers.async("mocp -i", function(f)
moc_now = {
state = "N/A",
file = "N/A",
artist = "N/A",
title = "N/A",
album = "N/A",
elapsed = "N/A",
total = "N/A"
}
function moc.update()
helpers.async('mocp -i', function(f)
moc_now = {
state = 'N/A',
file = 'N/A',
artist = 'N/A',
title = 'N/A',
album = 'N/A',
elapsed = 'N/A',
total = 'N/A',
}
for line in string.gmatch(f, "[^\n]+") do
for k, v in string.gmatch(line, "([%w]+):[%s](.*)$") do
if k == "State" then moc_now.state = v
elseif k == "File" then moc_now.file = v
elseif k == "Artist" then moc_now.artist = escape_f(v)
elseif k == "SongTitle" then moc_now.title = escape_f(v)
elseif k == "Album" then moc_now.album = escape_f(v)
elseif k == "CurrentTime" then moc_now.elapsed = escape_f(v)
elseif k == "TotalTime" then moc_now.total = escape_f(v)
end
end
end
for line in string.gmatch(f, '[^\n]+') do
for k, v in string.gmatch(line, '([%w]+):[%s](.*)$') do
if k == 'State' then
moc_now.state = v
elseif k == 'File' then
moc_now.file = v
elseif k == 'Artist' then
moc_now.artist = escape_f(v)
elseif k == 'SongTitle' then
moc_now.title = escape_f(v)
elseif k == 'Album' then
moc_now.album = escape_f(v)
elseif k == 'CurrentTime' then
moc_now.elapsed = escape_f(v)
elseif k == 'TotalTime' then
moc_now.total = escape_f(v)
end
end
end
moc_notification_preset.text = string.format("%s (%s) - %s\n%s", moc_now.artist,
moc_now.album, moc_now.total, moc_now.title)
widget = moc.widget
settings()
moc_notification_preset.text =
string.format('%s (%s) - %s\n%s', moc_now.artist, moc_now.album, moc_now.total, moc_now.title)
widget = moc.widget
settings()
if moc_now.state == "PLAY" then
if moc_now.title ~= helpers.get_map("current moc track") then
helpers.set_map("current moc track", moc_now.title)
if moc_now.state == 'PLAY' then
if moc_now.title ~= helpers.get_map('current moc track') then
helpers.set_map('current moc track', moc_now.title)
if followtag then moc_notification_preset.screen = focused() end
if followtag then
moc_notification_preset.screen = focused()
end
local common = {
preset = moc_notification_preset,
icon = default_art,
icon_size = cover_size,
replaces_id = moc.id,
}
local common = {
preset = moc_notification_preset,
icon = default_art,
icon_size = cover_size,
replaces_id = moc.id,
}
local path = string.format("%s/%s", music_dir, string.match(moc_now.file, ".*/"))
local cover = string.format("find '%s' -maxdepth 1 -type f | egrep -i -m1 '%s'", path, cover_pattern)
helpers.async({ shell, "-c", cover }, function(current_icon)
common.icon = current_icon:gsub("\n", "")
moc.id = naughty.notify(common).id
end)
end
elseif moc_now.state ~= "PAUSE" then
helpers.set_map("current moc track", nil)
end
end)
end
local path = string.format('%s/%s', music_dir, string.match(moc_now.file, '.*/'))
local cover =
string.format("find '%s' -maxdepth 1 -type f | egrep -i -m1 '%s'", path, cover_pattern)
helpers.async({ shell, '-c', cover }, function(current_icon)
common.icon = current_icon:gsub('\n', '')
moc.id = naughty.notify(common).id
end)
end
elseif moc_now.state ~= 'PAUSE' then
helpers.set_map('current moc track', nil)
end
end)
end
moc.timer = helpers.newtimer("moc", timeout, moc.update, true, true)
moc.timer = helpers.newtimer('moc', timeout, moc.update, true, true)
return moc
return moc
end
return factory

View File

@ -6,36 +6,36 @@
--]]
local async = require("lain.helpers").async
local awful = require("awful")
local async = require('lain.helpers').async
local awful = require('awful')
local execute = os.execute
local type = type
local type = type
-- Redshift
-- lain.widget.contrib.redshift
local redshift = { active = false, pid = nil }
function redshift.start()
execute("pkill redshift")
awful.spawn.with_shell("redshift -x") -- clear adjustments
redshift.pid = awful.spawn.with_shell("redshift")
redshift.active = true
if type(redshift.update_fun) == "function" then
redshift.update_fun(redshift.active)
end
execute('pkill redshift')
awful.spawn.with_shell('redshift -x') -- clear adjustments
redshift.pid = awful.spawn.with_shell('redshift')
redshift.active = true
if type(redshift.update_fun) == 'function' then
redshift.update_fun(redshift.active)
end
end
function redshift.toggle()
async({ awful.util.shell, "-c", string.format("ps -p %d -o pid=", redshift.pid) }, function(f)
if f and #f > 0 then -- redshift is running
-- Sending -USR1 toggles redshift (See project website)
execute("pkill -USR1 redshift")
redshift.active = not redshift.active
else -- not started or killed, (re)start it
redshift.start()
end
redshift.update_fun(redshift.active)
end)
async({ awful.util.shell, '-c', string.format('ps -p %d -o pid=', redshift.pid) }, function(f)
if f and #f > 0 then -- redshift is running
-- Sending -USR1 toggles redshift (See project website)
execute('pkill -USR1 redshift')
redshift.active = not redshift.active
else -- not started or killed, (re)start it
redshift.start()
end
redshift.update_fun(redshift.active)
end)
end
-- Attach to a widget
@ -44,11 +44,15 @@ end
-- @param fun: Function to be run each time redshift is toggled (optional).
-- Use it to update widget text or icons on status change.
function redshift.attach(widget, fun)
redshift.update_fun = fun or function() end
if not redshift.pid then redshift.start() end
if widget then
widget:buttons(awful.util.table.join(awful.button({}, 1, function () redshift.toggle() end)))
end
redshift.update_fun = fun or function() end
if not redshift.pid then
redshift.start()
end
if widget then
widget:buttons(awful.util.table.join(awful.button({}, 1, function()
redshift.toggle()
end)))
end
end
return redshift

View File

@ -5,88 +5,92 @@
--]]
local helpers = require("lain.helpers")
local markup = require("lain.util").markup
local awful = require("awful")
local naughty = require("naughty")
local mouse = mouse
local helpers = require('lain.helpers')
local markup = require('lain.util').markup
local awful = require('awful')
local naughty = require('naughty')
local mouse = mouse
-- Taskwarrior notification
-- lain.widget.contrib.task
local task = {}
function task.hide()
if not task.notification then return end
naughty.destroy(task.notification)
task.notification = nil
if not task.notification then
return
end
naughty.destroy(task.notification)
task.notification = nil
end
function task.show(scr)
task.notification_preset.screen = task.followtag and awful.screen.focused() or scr or 1
task.notification_preset.screen = task.followtag and awful.screen.focused() or scr or 1
helpers.async({ awful.util.shell, "-c", task.show_cmd }, function(f)
local widget_focused = true
helpers.async({ awful.util.shell, '-c', task.show_cmd }, function(f)
local widget_focused = true
if mouse.current_widgets then
widget_focused = false
for _,v in ipairs(mouse.current_widgets) do
if task.widget == v then
widget_focused = true
break
end
end
end
if mouse.current_widgets then
widget_focused = false
for _, v in ipairs(mouse.current_widgets) do
if task.widget == v then
widget_focused = true
break
end
end
end
if widget_focused then
task.hide()
task.notification = naughty.notify {
preset = task.notification_preset,
title = "task next",
text = markup.font(task.notification_preset.font,
awful.util.escape(f:gsub("\n*$", "")))
}
end
end)
if widget_focused then
task.hide()
task.notification = naughty.notify({
preset = task.notification_preset,
title = 'task next',
text = markup.font(task.notification_preset.font, awful.util.escape(f:gsub('\n*$', ''))),
})
end
end)
end
function task.prompt()
awful.prompt.run {
prompt = task.prompt_text,
textbox = awful.screen.focused().mypromptbox.widget,
exe_callback = function(t)
helpers.async(t, function(f)
naughty.notify {
preset = task.notification_preset,
title = t,
text = markup.font(task.notification_preset.font,
awful.util.escape(f:gsub("\n*$", "")))
}
end)
end,
history_path = awful.util.getdir("cache") .. "/history_task"
}
awful.prompt.run({
prompt = task.prompt_text,
textbox = awful.screen.focused().mypromptbox.widget,
exe_callback = function(t)
helpers.async(t, function(f)
naughty.notify({
preset = task.notification_preset,
title = t,
text = markup.font(task.notification_preset.font, awful.util.escape(f:gsub('\n*$', ''))),
})
end)
end,
history_path = awful.util.getdir('cache') .. '/history_task',
})
end
function task.attach(widget, args)
args = args or {}
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.widget = widget
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.widget = widget
if not task.notification_preset then
task.notification_preset = {
font = "Monospace 10",
icon = helpers.icons_dir .. "/taskwarrior.png"
}
end
if not task.notification_preset then
task.notification_preset = {
font = 'Monospace 10',
icon = helpers.icons_dir .. '/taskwarrior.png',
}
end
if widget then
widget:connect_signal("mouse::enter", function () task.show() end)
widget:connect_signal("mouse::leave", function () task.hide() end)
end
if widget then
widget:connect_signal('mouse::enter', function()
task.show()
end)
widget:connect_signal('mouse::leave', function()
task.hide()
end)
end
end
return task

View File

@ -6,142 +6,159 @@
--]]
local helpers = require("lain.helpers")
local focused = require("awful.screen").focused
local naughty = require("naughty")
local wibox = require("wibox")
local string = string
local type = type
local helpers = require('lain.helpers')
local focused = require('awful.screen').focused
local naughty = require('naughty')
local wibox = require('wibox')
local string = string
local type = type
-- ThinkPad battery infos and widget creator
-- http://www.thinkwiki.org/wiki/Tp_smapi
-- lain.widget.contrib.tp_smapi
local function factory(apipath)
local tp_smapi = {
path = apipath or "/sys/devices/platform/smapi"
}
local tp_smapi = {
path = apipath or '/sys/devices/platform/smapi',
}
function tp_smapi.get(batid, feature)
return helpers.first_line(string.format("%s/%s/%s", tp_smapi.path, batid or "BAT0", feature or ""))
end
function tp_smapi.get(batid, feature)
return helpers.first_line(string.format('%s/%s/%s', tp_smapi.path, batid or 'BAT0', feature or ''))
end
function tp_smapi.installed(batid)
return tp_smapi.get(batid, "installed") == "1"
end
function tp_smapi.installed(batid)
return tp_smapi.get(batid, 'installed') == '1'
end
function tp_smapi.status(batid)
return tp_smapi.get(batid, "state")
end
function tp_smapi.status(batid)
return tp_smapi.get(batid, 'state')
end
function tp_smapi.percentage(batid)
return tp_smapi.get(batid, "remaining_percent")
end
function tp_smapi.percentage(batid)
return tp_smapi.get(batid, 'remaining_percent')
end
-- either running or charging time
function tp_smapi.time(batid)
local status = tp_smapi.status(batid)
local mins_left = tp_smapi.get(batid, string.match(string.lower(status), "discharging") and "remaining_running_time" or "remaining_charging_time")
if not string.find(mins_left, "^%d+") then return "N/A" end
return string.format("%02d:%02d", math.floor(mins_left / 60), mins_left % 60) -- HH:mm
end
-- either running or charging time
function tp_smapi.time(batid)
local status = tp_smapi.status(batid)
local mins_left = tp_smapi.get(
batid,
string.match(string.lower(status), 'discharging') and 'remaining_running_time' or 'remaining_charging_time'
)
if not string.find(mins_left, '^%d+') then
return 'N/A'
end
return string.format('%02d:%02d', math.floor(mins_left / 60), mins_left % 60) -- HH:mm
end
function tp_smapi.hide()
if not tp_smapi.notification then return end
naughty.destroy(tp_smapi.notification)
tp_smapi.notification = nil
end
function tp_smapi.hide()
if not tp_smapi.notification then
return
end
naughty.destroy(tp_smapi.notification)
tp_smapi.notification = nil
end
function tp_smapi.show(batid, seconds, scr)
if not tp_smapi.installed(batid) then return end
function tp_smapi.show(batid, seconds, scr)
if not tp_smapi.installed(batid) then
return
end
local mfgr = tp_smapi.get(batid, "manufacturer") or "no_mfgr"
local model = tp_smapi.get(batid, "model") or "no_model"
local chem = tp_smapi.get(batid, "chemistry") or "no_chem"
local status = tp_smapi.get(batid, "state")
local time = tp_smapi.time(batid)
local msg
local mfgr = tp_smapi.get(batid, 'manufacturer') or 'no_mfgr'
local model = tp_smapi.get(batid, 'model') or 'no_model'
local chem = tp_smapi.get(batid, 'chemistry') or 'no_chem'
local status = tp_smapi.get(batid, 'state')
local time = tp_smapi.time(batid)
local msg
if status and status ~= "idle" then
msg = string.format("[%s] %s %s", status, time ~= "N/A" and time or "unknown remaining time",
string.lower(status):gsub(" ", ""):gsub("\n", "") == "charging" and " until charged" or " remaining")
else
msg = "On AC power"
end
if status and status ~= 'idle' then
msg = string.format(
'[%s] %s %s',
status,
time ~= 'N/A' and time or 'unknown remaining time',
string.lower(status):gsub(' ', ''):gsub('\n', '') == 'charging' and ' until charged' or ' remaining'
)
else
msg = 'On AC power'
end
tp_smapi.hide()
tp_smapi.notification = naughty.notify {
title = string.format("%s: %s %s (%s)", batid, mfgr, model, chem),
text = msg,
timeout = type(seconds) == "number" and seconds or 0,
screen = scr or focused()
}
end
tp_smapi.hide()
tp_smapi.notification = naughty.notify({
title = string.format('%s: %s %s (%s)', batid, mfgr, model, chem),
text = msg,
timeout = type(seconds) == 'number' and seconds or 0,
screen = scr or focused(),
})
end
function tp_smapi.create_widget(args)
args = args or {}
function tp_smapi.create_widget(args)
args = args or {}
local pspath = args.pspath or "/sys/class/power_supply/"
local batteries = args.batteries or (args.battery and {args.battery}) or {}
local timeout = args.timeout or 30
local settings = args.settings or function() end
local pspath = args.pspath or '/sys/class/power_supply/'
local batteries = args.batteries or (args.battery and { args.battery }) or {}
local timeout = args.timeout or 30
local settings = args.settings or function() end
if #batteries == 0 then
helpers.line_callback("ls -1 " .. pspath, function(line)
local bstr = string.match(line, "BAT%w+")
if bstr then batteries[#batteries + 1] = bstr end
end)
end
if #batteries == 0 then
helpers.line_callback('ls -1 ' .. pspath, function(line)
local bstr = string.match(line, 'BAT%w+')
if bstr then
batteries[#batteries + 1] = bstr
end
end)
end
local all_batteries_installed = true
local all_batteries_installed = true
for _, battery in ipairs(batteries) do
if not tp_smapi.installed(battery) then
naughty.notify {
preset = naughty.config.critical,
title = "tp_smapi: error while creating widget",
text = string.format("battery %s is not installed", battery)
}
all_batteries_installed = false
break
end
end
for _, battery in ipairs(batteries) do
if not tp_smapi.installed(battery) then
naughty.notify({
preset = naughty.config.critical,
title = 'tp_smapi: error while creating widget',
text = string.format('battery %s is not installed', battery),
})
all_batteries_installed = false
break
end
end
if not all_batteries_installed then return end
if not all_batteries_installed then
return
end
tpbat = {
batteries = batteries,
widget = args.widget or wibox.widget.textbox()
}
tpbat = {
batteries = batteries,
widget = args.widget or wibox.widget.textbox(),
}
function tpbat.update()
tpbat_now = {
n_status = {},
n_perc = {},
n_time = {},
status = "N/A"
}
function tpbat.update()
tpbat_now = {
n_status = {},
n_perc = {},
n_time = {},
status = 'N/A',
}
for i = 1, #batteries do
tpbat_now.n_status[i] = tp_smapi.status(batteries[i]) or "N/A"
tpbat_now.n_perc[i] = tp_smapi.percentage(batteries[i])
tpbat_now.n_time[i] = tp_smapi.time(batteries[i]) or "N/A"
for i = 1, #batteries do
tpbat_now.n_status[i] = tp_smapi.status(batteries[i]) or 'N/A'
tpbat_now.n_perc[i] = tp_smapi.percentage(batteries[i])
tpbat_now.n_time[i] = tp_smapi.time(batteries[i]) or 'N/A'
if not tpbat_now.n_status[i]:lower():match("full") then
tpbat_now.status = tpbat_now.n_status[i]
end
end
if not tpbat_now.n_status[i]:lower():match('full') then
tpbat_now.status = tpbat_now.n_status[i]
end
end
widget = tpbat.widget -- backwards compatibility
settings()
end
widget = tpbat.widget -- backwards compatibility
settings()
end
helpers.newtimer("thinkpad-batteries", timeout, tpbat.update)
helpers.newtimer('thinkpad-batteries', timeout, tpbat.update)
return tpbat
end
return tpbat
end
return tp_smapi
return tp_smapi
end
return factory

View File

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

View File

@ -7,150 +7,168 @@
--]]
local helpers = require("lain.helpers")
local Gio = require("lgi").Gio
local focused = require("awful.screen").focused
local wibox = require("wibox")
local naughty = require("naughty")
local gears = require("gears")
local math = math
local string = string
local tconcat = table.concat
local type = type
local helpers = require('lain.helpers')
local Gio = require('lgi').Gio
local focused = require('awful.screen').focused
local wibox = require('wibox')
local naughty = require('naughty')
local gears = require('gears')
local math = math
local string = string
local tconcat = table.concat
local type = type
local query_size = Gio.FILE_ATTRIBUTE_FILESYSTEM_SIZE
local query_free = Gio.FILE_ATTRIBUTE_FILESYSTEM_FREE
local query_used = Gio.FILE_ATTRIBUTE_FILESYSTEM_USED
local query = query_size .. "," .. query_free .. "," .. query_used
local query = query_size .. ',' .. query_free .. ',' .. query_used
-- File systems info
-- lain.widget.fs
local function factory(args)
args = args or {}
args = args or {}
local fs = {
widget = args.widget or wibox.widget.textbox(),
units = {
[1] = "Kb", [2] = "Mb", [3] = "Gb",
[4] = "Tb", [5] = "Pb", [6] = "Eb",
[7] = "Zb", [8] = "Yb"
}
}
local fs = {
widget = args.widget or wibox.widget.textbox(),
units = {
[1] = 'Kb',
[2] = 'Mb',
[3] = 'Gb',
[4] = 'Tb',
[5] = 'Pb',
[6] = 'Eb',
[7] = 'Zb',
[8] = 'Yb',
},
}
function fs.hide()
if not fs.notification then return end
naughty.destroy(fs.notification)
fs.notification = nil
end
function fs.hide()
if not fs.notification then
return
end
naughty.destroy(fs.notification)
fs.notification = nil
end
function fs.show(seconds, scr)
fs.hide()
fs.update(function()
fs.notification_preset.screen = fs.followtag and focused() or scr or 1
fs.notification = naughty.notify {
preset = fs.notification_preset,
timeout = type(seconds) == "number" and seconds or 5
}
end)
end
function fs.show(seconds, scr)
fs.hide()
fs.update(function()
fs.notification_preset.screen = fs.followtag and focused() or scr or 1
fs.notification = naughty.notify({
preset = fs.notification_preset,
timeout = type(seconds) == 'number' and seconds or 5,
})
end)
end
local timeout = args.timeout or 600
local partition = args.partition
local threshold = args.threshold or 99
local showpopup = args.showpopup or "on"
local settings = args.settings or function() end
local timeout = args.timeout or 600
local partition = args.partition
local threshold = args.threshold or 99
local showpopup = args.showpopup or 'on'
local settings = args.settings or function() end
fs.followtag = args.followtag or false
fs.notification_preset = args.notification_preset
fs.followtag = args.followtag or false
fs.notification_preset = args.notification_preset
if not fs.notification_preset then
fs.notification_preset = {
font = "Monospace 10",
fg = "#FFFFFF",
bg = "#000000"
}
end
if not fs.notification_preset then
fs.notification_preset = {
font = 'Monospace 10',
fg = '#FFFFFF',
bg = '#000000',
}
end
local function update_synced()
local pathlen = 10
fs_now = {}
local function update_synced()
local pathlen = 10
fs_now = {}
local notifypaths = {}
for _, mount in ipairs(Gio.unix_mounts_get()) do
local path = Gio.unix_mount_get_mount_path(mount)
local root = Gio.File.new_for_path(path)
local info = root:query_filesystem_info(query)
local notifypaths = {}
for _, mount in ipairs(Gio.unix_mounts_get()) do
local path = Gio.unix_mount_get_mount_path(mount)
local root = Gio.File.new_for_path(path)
local info = root:query_filesystem_info(query)
if info then
local size = info:get_attribute_uint64(query_size)
local used = info:get_attribute_uint64(query_used)
local free = info:get_attribute_uint64(query_free)
if info then
local size = info:get_attribute_uint64(query_size)
local used = info:get_attribute_uint64(query_used)
local free = info:get_attribute_uint64(query_free)
if size > 0 then
local units = math.floor(math.log(size)/math.log(1024))
if size > 0 then
local units = math.floor(math.log(size) / math.log(1024))
fs_now[path] = {
units = fs.units[units],
percentage = math.floor(100 * used / size), -- used percentage
size = size / math.pow(1024, units),
used = used / math.pow(1024, units),
free = free / math.pow(1024, units)
}
fs_now[path] = {
units = fs.units[units],
percentage = math.floor(100 * used / size), -- used percentage
size = size / math.pow(1024, units),
used = used / math.pow(1024, units),
free = free / math.pow(1024, units),
}
if fs_now[path].percentage > 0 then -- don't notify unused file systems
notifypaths[#notifypaths+1] = path
if fs_now[path].percentage > 0 then -- don't notify unused file systems
notifypaths[#notifypaths + 1] = path
if #path > pathlen then
pathlen = #path
end
end
end
end
end
if #path > pathlen then
pathlen = #path
end
end
end
end
end
widget = fs.widget
settings()
widget = fs.widget
settings()
if partition and fs_now[partition] and fs_now[partition].percentage >= threshold then
if not helpers.get_map(partition) then
naughty.notify {
preset = naughty.config.presets.critical,
title = "Warning",
text = string.format("%s is above %d%% (%d%%)", partition, threshold, fs_now[partition].percentage)
}
helpers.set_map(partition, true)
else
helpers.set_map(partition, false)
end
end
if partition and fs_now[partition] and fs_now[partition].percentage >= threshold then
if not helpers.get_map(partition) then
naughty.notify({
preset = naughty.config.presets.critical,
title = 'Warning',
text = string.format('%s is above %d%% (%d%%)', partition, threshold, fs_now[partition].percentage),
})
helpers.set_map(partition, true)
else
helpers.set_map(partition, false)
end
end
local fmt = "%-" .. tostring(pathlen) .. "s %4s\t%6s\t%6s\n"
local notifytable = { [1] = string.format(fmt, "path", "used", "free", "size") }
fmt = "\n%-" .. tostring(pathlen) .. "s %3s%%\t%6.2f\t%6.2f %s"
for _, path in ipairs(notifypaths) do
notifytable[#notifytable+1] = string.format(fmt, path, fs_now[path].percentage, fs_now[path].free, fs_now[path].size, fs_now[path].units)
end
local fmt = '%-' .. tostring(pathlen) .. 's %4s\t%6s\t%6s\n'
local notifytable = { [1] = string.format(fmt, 'path', 'used', 'free', 'size') }
fmt = '\n%-' .. tostring(pathlen) .. 's %3s%%\t%6.2f\t%6.2f %s'
for _, path in ipairs(notifypaths) do
notifytable[#notifytable + 1] = string.format(
fmt,
path,
fs_now[path].percentage,
fs_now[path].free,
fs_now[path].size,
fs_now[path].units
)
end
fs.notification_preset.text = tconcat(notifytable)
end
fs.notification_preset.text = tconcat(notifytable)
end
function fs.update(callback)
Gio.Async.start(gears.protected_call.call)(function()
update_synced()
if type(callback) == "function" and callback then
callback()
end
end)
end
function fs.update(callback)
Gio.Async.start(gears.protected_call.call)(function()
update_synced()
if type(callback) == 'function' and callback then
callback()
end
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
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 or "fs", timeout, fs.update)
helpers.newtimer(partition or 'fs', timeout, fs.update)
return fs
return fs
end
return factory

View File

@ -5,90 +5,111 @@
--]]
local helpers = require("lain.helpers")
local naughty = require("naughty")
local wibox = require("wibox")
local awful = require("awful")
local string = string
local type = type
local helpers = require('lain.helpers')
local naughty = require('naughty')
local wibox = require('wibox')
local awful = require('awful')
local string = string
local type = type
local tonumber = tonumber
-- Mail IMAP check
-- lain.widget.imap
local function factory(args)
args = args or {}
args = args or {}
local imap = { widget = args.widget or wibox.widget.textbox() }
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 pwdtimeout = args.pwdtimeout or 10
local is_plain = args.is_plain or false
local followtag = args.followtag or false
local notify = args.notify or "on"
local settings = args.settings or function() end
local imap = { widget = args.widget or wibox.widget.textbox() }
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 pwdtimeout = args.pwdtimeout or 10
local is_plain = args.is_plain or false
local followtag = args.followtag or false
local notify = args.notify or 'on'
local settings = args.settings or function() end
local head_command = "curl --connect-timeout 3 -fsm 3"
local request = "-X 'STATUS INBOX (MESSAGES RECENT UNSEEN)'"
local head_command = 'curl --connect-timeout 3 -fsm 3'
local request = "-X 'STATUS INBOX (MESSAGES RECENT UNSEEN)'"
if not server or not mail or not password then return end
if not server or not mail or not password then
return
end
mail_notification_preset = {
icon = helpers.icons_dir .. "mail.png",
position = "top_left"
}
mail_notification_preset = {
icon = helpers.icons_dir .. 'mail.png',
position = 'top_left',
}
helpers.set_map(mail, 0)
helpers.set_map(mail, 0)
if not is_plain then
if type(password) == "string" or type(password) == "table" then
helpers.async(password, function(f) password = f:gsub("\n", "") end)
elseif type(password) == "function" then
imap.pwdtimer = helpers.newtimer(mail .. "-password", pwdtimeout, function()
local retrieved_password, try_again = password()
if not try_again then
imap.pwdtimer:stop() -- stop trying to retrieve
password = retrieved_password or "" -- failsafe
end
end, true, true)
end
end
if not is_plain then
if type(password) == 'string' or type(password) == 'table' then
helpers.async(password, function(f)
password = f:gsub('\n', '')
end)
elseif type(password) == 'function' then
imap.pwdtimer = helpers.newtimer(mail .. '-password', pwdtimeout, function()
local retrieved_password, try_again = password()
if not try_again then
imap.pwdtimer:stop() -- stop trying to retrieve
password = retrieved_password or '' -- failsafe
end
end, true, true)
end
end
function imap.update()
-- do not update if the password has not been retrieved yet
if type(password) ~= "string" then return end
function imap.update()
-- do not update if the password has not been retrieved yet
if type(password) ~= 'string' then
return
end
local curl = string.format("%s --url imaps://%s:%s/INBOX -u %s:'%s' %s -k",
head_command, server, port, mail, password, request)
local curl = string.format(
"%s --url imaps://%s:%s/INBOX -u %s:'%s' %s -k",
head_command,
server,
port,
mail,
password,
request
)
helpers.async(curl, function(f)
imap_now = { ["MESSAGES"] = 0, ["RECENT"] = 0, ["UNSEEN"] = 0 }
helpers.async(curl, function(f)
imap_now = { ['MESSAGES'] = 0, ['RECENT'] = 0, ['UNSEEN'] = 0 }
for s,d in f:gmatch("(%w+)%s+(%d+)") do imap_now[s] = tonumber(d) end
mailcount = imap_now["UNSEEN"] -- backwards compatibility
widget = imap.widget
for s, d in f:gmatch('(%w+)%s+(%d+)') do
imap_now[s] = tonumber(d)
end
mailcount = imap_now['UNSEEN'] -- backwards compatibility
widget = imap.widget
settings()
settings()
if notify == "on" and mailcount and mailcount >= 1 and mailcount > helpers.get_map(mail) then
if followtag then mail_notification_preset.screen = awful.screen.focused() end
naughty.notify {
preset = mail_notification_preset,
text = string.format("%s has <b>%d</b> new message%s", mail, mailcount, mailcount == 1 and "" or "s")
}
end
if notify == 'on' and mailcount and mailcount >= 1 and mailcount > helpers.get_map(mail) then
if followtag then
mail_notification_preset.screen = awful.screen.focused()
end
naughty.notify({
preset = mail_notification_preset,
text = string.format(
'%s has <b>%d</b> new message%s',
mail,
mailcount,
mailcount == 1 and '' or 's'
),
})
end
helpers.set_map(mail, imap_now["UNSEEN"])
end)
helpers.set_map(mail, imap_now['UNSEEN'])
end)
end
end
imap.timer = helpers.newtimer(mail, timeout, imap.update, true, true)
imap.timer = helpers.newtimer(mail, timeout, imap.update, true, true)
return imap
return imap
end
return factory

View File

@ -11,9 +11,9 @@
--]]
local wrequire = require("lain.helpers").wrequire
local wrequire = require('lain.helpers').wrequire
local setmetatable = setmetatable
local widget = { _NAME = "lain.widget" }
local widget = { _NAME = 'lain.widget' }
return setmetatable(widget, { __index = wrequire })

View File

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

View File

@ -6,130 +6,154 @@
--]]
local helpers = require("lain.helpers")
local shell = require("awful.util").shell
local escape_f = require("awful.util").escape
local focused = require("awful.screen").focused
local naughty = require("naughty")
local wibox = require("wibox")
local os = os
local string = string
local helpers = require('lain.helpers')
local shell = require('awful.util').shell
local escape_f = require('awful.util').escape
local focused = require('awful.screen').focused
local naughty = require('naughty')
local wibox = require('wibox')
local os = os
local string = string
-- MPD infos
-- lain.widget.mpd
local function factory(args)
args = args or {}
args = args or {}
local mpd = { widget = args.widget or wibox.widget.textbox() }
local timeout = args.timeout or 2
local password = (args.password and #args.password > 0 and string.format("password %s\\n", args.password)) or ""
local host = args.host or os.getenv("MPD_HOST") or "127.0.0.1"
local port = args.port or os.getenv("MPD_PORT") or "6600"
local music_dir = args.music_dir or os.getenv("HOME") .. "/Music"
local cover_pattern = args.cover_pattern or "*\\.(jpg|jpeg|png|gif)$"
local cover_size = args.cover_size or 100
local default_art = args.default_art
local notify = args.notify or "on"
local followtag = args.followtag or false
local settings = args.settings or function() end
local mpd = { widget = args.widget or wibox.widget.textbox() }
local timeout = args.timeout or 2
local password = (args.password and #args.password > 0 and string.format('password %s\\n', args.password)) or ''
local host = args.host or os.getenv('MPD_HOST') or '127.0.0.1'
local port = args.port or os.getenv('MPD_PORT') or '6600'
local music_dir = args.music_dir or os.getenv('HOME') .. '/Music'
local cover_pattern = args.cover_pattern or '*\\.(jpg|jpeg|png|gif)$'
local cover_size = args.cover_size or 100
local default_art = args.default_art
local notify = args.notify or 'on'
local followtag = args.followtag or false
local settings = args.settings or function() end
local mpdh = string.format("telnet://%s:%s", host, port)
local echo = string.format("printf \"%sstatus\\ncurrentsong\\nclose\\n\"", password)
local cmd = string.format("%s | curl --connect-timeout 1 -fsm 3 %s", echo, mpdh)
local mpdh = string.format('telnet://%s:%s', host, port)
local echo = string.format('printf "%sstatus\\ncurrentsong\\nclose\\n"', password)
local cmd = string.format('%s | curl --connect-timeout 1 -fsm 3 %s', echo, mpdh)
mpd_notification_preset = { title = "Now playing", timeout = 6 }
mpd_notification_preset = { title = 'Now playing', timeout = 6 }
helpers.set_map("current mpd track", nil)
helpers.set_map('current mpd track', nil)
function mpd.update()
helpers.async({ shell, "-c", cmd }, 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",
artist = "N/A",
title = "N/A",
album = "N/A",
genre = "N/A",
track = "N/A",
date = "N/A",
time = "N/A",
elapsed = "N/A",
volume = "N/A"
}
function mpd.update()
helpers.async({ shell, '-c', cmd }, 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',
artist = 'N/A',
title = 'N/A',
album = 'N/A',
genre = 'N/A',
track = 'N/A',
date = 'N/A',
time = 'N/A',
elapsed = 'N/A',
volume = 'N/A',
}
for line in string.gmatch(f, "[^\n]+") do
for k, v in string.gmatch(line, "([%w]+):[%s](.*)$") do
if k == "state" then mpd_now.state = v
elseif k == "file" then mpd_now.file = v
elseif k == "Name" then mpd_now.name = 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 == "Album" then mpd_now.album = escape_f(v)
elseif k == "Genre" then mpd_now.genre = escape_f(v)
elseif k == "Track" then mpd_now.track = escape_f(v)
elseif k == "Date" then mpd_now.date = escape_f(v)
elseif k == "Time" then mpd_now.time = v
elseif k == "elapsed" then mpd_now.elapsed = string.match(v, "%d+")
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"
elseif k == "volume" then mpd_now.volume = v
end
end
end
for line in string.gmatch(f, '[^\n]+') do
for k, v in string.gmatch(line, '([%w]+):[%s](.*)$') do
if k == 'state' then
mpd_now.state = v
elseif k == 'file' then
mpd_now.file = v
elseif k == 'Name' then
mpd_now.name = 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 == 'Album' then
mpd_now.album = escape_f(v)
elseif k == 'Genre' then
mpd_now.genre = escape_f(v)
elseif k == 'Track' then
mpd_now.track = escape_f(v)
elseif k == 'Date' then
mpd_now.date = escape_f(v)
elseif k == 'Time' then
mpd_now.time = v
elseif k == 'elapsed' then
mpd_now.elapsed = string.match(v, '%d+')
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'
elseif k == 'volume' then
mpd_now.volume = v
end
end
end
mpd_notification_preset.text = string.format("%s (%s) - %s\n%s", mpd_now.artist,
mpd_now.album, mpd_now.date, mpd_now.title)
widget = mpd.widget
settings()
mpd_notification_preset.text =
string.format('%s (%s) - %s\n%s', mpd_now.artist, mpd_now.album, mpd_now.date, mpd_now.title)
widget = mpd.widget
settings()
if mpd_now.state == "play" then
if notify == "on" and mpd_now.title ~= helpers.get_map("current mpd track") then
helpers.set_map("current mpd track", mpd_now.title)
if mpd_now.state == 'play' then
if notify == 'on' and mpd_now.title ~= helpers.get_map('current mpd track') then
helpers.set_map('current mpd track', mpd_now.title)
if followtag then mpd_notification_preset.screen = focused() end
if followtag then
mpd_notification_preset.screen = focused()
end
local common = {
preset = mpd_notification_preset,
icon = default_art,
icon_size = cover_size,
replaces_id = mpd.id
}
local common = {
preset = mpd_notification_preset,
icon = default_art,
icon_size = cover_size,
replaces_id = mpd.id,
}
if not string.match(mpd_now.file, "http.*://") then -- local file instead of http stream
local path = string.format("%s/%s", music_dir, string.match(mpd_now.file, ".*/"))
local cover = string.format("find '%s' -maxdepth 1 -type f | egrep -i -m1 '%s'",
path:gsub("'", "'\\''"), cover_pattern)
helpers.async({ shell, "-c", cover }, function(current_icon)
common.icon = current_icon:gsub("\n", "")
if #common.icon == 0 then common.icon = nil end
mpd.id = naughty.notify(common).id
end)
else
mpd.id = naughty.notify(common).id
end
if 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
elseif mpd_now.state ~= 'pause' then
helpers.set_map('current mpd track', nil)
end
end)
end
end
elseif mpd_now.state ~= "pause" then
helpers.set_map("current mpd track", nil)
end
end)
end
mpd.timer = helpers.newtimer('mpd', timeout, mpd.update, true, true)
mpd.timer = helpers.newtimer("mpd", timeout, mpd.update, true, true)
return mpd
return mpd
end
return factory

View File

@ -6,117 +6,128 @@
--]]
local helpers = require("lain.helpers")
local naughty = require("naughty")
local wibox = require("wibox")
local string = string
local helpers = require('lain.helpers')
local naughty = require('naughty')
local wibox = require('wibox')
local string = string
-- Network infos
-- lain.widget.net
local function factory(args)
args = args or {}
args = args or {}
local net = { widget = args.widget or wibox.widget.textbox(), devices = {} }
local timeout = args.timeout or 2
local units = args.units or 1024 -- KB
local notify = args.notify or "on"
local wifi_state = args.wifi_state or "off"
local eth_state = args.eth_state or "off"
local screen = args.screen or 1
local format = args.format or "%.1f"
local settings = args.settings or function() end
local net = { widget = args.widget or wibox.widget.textbox(), devices = {} }
local timeout = args.timeout or 2
local units = args.units or 1024 -- KB
local notify = args.notify or 'on'
local wifi_state = args.wifi_state or 'off'
local eth_state = args.eth_state or 'off'
local screen = args.screen or 1
local format = args.format or '%.1f'
local settings = args.settings or function() end
-- Compatibility with old API where iface was a string corresponding to 1 interface
net.iface = (args.iface and (type(args.iface) == "string" and {args.iface}) or
(type(args.iface) == "table" and args.iface)) or {}
-- Compatibility with old API where iface was a string corresponding to 1 interface
net.iface = (
args.iface and (type(args.iface) == 'string' and { args.iface })
or (type(args.iface) == 'table' and args.iface)
) or {}
function net.get_devices()
net.iface = {} -- reset at every call
helpers.line_callback("ip link", function(line)
net.iface[#net.iface + 1] = not string.match(line, "LOOPBACK") and string.match(line, "(%w+): <") or nil
end)
end
function net.get_devices()
net.iface = {} -- reset at every call
helpers.line_callback('ip link', function(line)
net.iface[#net.iface + 1] = not string.match(line, 'LOOPBACK') and string.match(line, '(%w+): <') or nil
end)
end
if #net.iface == 0 then net.get_devices() end
if #net.iface == 0 then
net.get_devices()
end
function net.update()
-- These are the totals over all specified interfaces
net_now = {
devices = {},
-- Bytes since last iteration
sent = 0,
received = 0
}
function net.update()
-- These are the totals over all specified interfaces
net_now = {
devices = {},
-- Bytes since last iteration
sent = 0,
received = 0,
}
for _, dev in ipairs(net.iface) do
local dev_now = {}
local dev_before = net.devices[dev] or { last_t = 0, last_r = 0 }
local now_t = tonumber(helpers.first_line(string.format("/sys/class/net/%s/statistics/tx_bytes", dev)) or 0)
local now_r = tonumber(helpers.first_line(string.format("/sys/class/net/%s/statistics/rx_bytes", dev)) or 0)
for _, dev in ipairs(net.iface) do
local dev_now = {}
local dev_before = net.devices[dev] or { last_t = 0, last_r = 0 }
local now_t = tonumber(helpers.first_line(string.format('/sys/class/net/%s/statistics/tx_bytes', dev)) or 0)
local now_r = tonumber(helpers.first_line(string.format('/sys/class/net/%s/statistics/rx_bytes', dev)) or 0)
dev_now.carrier = helpers.first_line(string.format("/sys/class/net/%s/carrier", dev)) or "0"
dev_now.state = helpers.first_line(string.format("/sys/class/net/%s/operstate", dev)) or "down"
dev_now.carrier = helpers.first_line(string.format('/sys/class/net/%s/carrier', dev)) or '0'
dev_now.state = helpers.first_line(string.format('/sys/class/net/%s/operstate', dev)) or 'down'
dev_now.sent = (now_t - dev_before.last_t) / timeout / units
dev_now.received = (now_r - dev_before.last_r) / timeout / units
dev_now.sent = (now_t - dev_before.last_t) / timeout / units
dev_now.received = (now_r - dev_before.last_r) / timeout / units
net_now.sent = net_now.sent + dev_now.sent
net_now.received = net_now.received + dev_now.received
net_now.sent = net_now.sent + dev_now.sent
net_now.received = net_now.received + dev_now.received
dev_now.sent = string.format(format, dev_now.sent)
dev_now.received = string.format(format, dev_now.received)
dev_now.sent = string.format(format, dev_now.sent)
dev_now.received = string.format(format, dev_now.received)
dev_now.last_t = now_t
dev_now.last_r = now_r
dev_now.last_t = now_t
dev_now.last_r = now_r
if wifi_state == "on" and helpers.first_line(string.format("/sys/class/net/%s/uevent", dev)) == "DEVTYPE=wlan" then
dev_now.wifi = true
if string.match(dev_now.carrier, "1") then
dev_now.signal = tonumber(string.match(helpers.lines_from("/proc/net/wireless")[3], "(%-%d+%.)")) or nil
end
else
dev_now.wifi = false
end
if
wifi_state == 'on'
and helpers.first_line(string.format('/sys/class/net/%s/uevent', dev)) == 'DEVTYPE=wlan'
then
dev_now.wifi = true
if string.match(dev_now.carrier, '1') then
dev_now.signal = tonumber(string.match(helpers.lines_from('/proc/net/wireless')[3], '(%-%d+%.)'))
or nil
end
else
dev_now.wifi = false
end
if eth_state == "on" and helpers.first_line(string.format("/sys/class/net/%s/uevent", dev)) ~= "DEVTYPE=wlan" then
dev_now.ethernet = true
else
dev_now.ethernet = false
end
if
eth_state == 'on'
and helpers.first_line(string.format('/sys/class/net/%s/uevent', dev)) ~= 'DEVTYPE=wlan'
then
dev_now.ethernet = true
else
dev_now.ethernet = false
end
net.devices[dev] = dev_now
net.devices[dev] = dev_now
-- Notify only once when connection is lost
if string.match(dev_now.carrier, "0") and notify == "on" and helpers.get_map(dev) then
naughty.notify {
title = dev,
text = "No carrier",
icon = helpers.icons_dir .. "no_net.png",
screen = screen
}
helpers.set_map(dev, false)
elseif string.match(dev_now.carrier, "1") then
helpers.set_map(dev, true)
end
-- Notify only once when connection is lost
if string.match(dev_now.carrier, '0') and notify == 'on' and helpers.get_map(dev) then
naughty.notify({
title = dev,
text = 'No carrier',
icon = helpers.icons_dir .. 'no_net.png',
screen = screen,
})
helpers.set_map(dev, false)
elseif string.match(dev_now.carrier, '1') then
helpers.set_map(dev, true)
end
net_now.carrier = dev_now.carrier
net_now.state = dev_now.state
net_now.devices[dev] = dev_now
-- net_now.sent and net_now.received will be
-- the totals across all specified devices
end
net_now.carrier = dev_now.carrier
net_now.state = dev_now.state
net_now.devices[dev] = dev_now
-- net_now.sent and net_now.received will be
-- the totals across all specified devices
end
net_now.sent = string.format(format, net_now.sent)
net_now.received = string.format(format, net_now.received)
net_now.sent = string.format(format, net_now.sent)
net_now.received = string.format(format, net_now.received)
widget = net.widget
settings()
end
widget = net.widget
settings()
end
helpers.newtimer("network", timeout, net.update)
helpers.newtimer('network', timeout, net.update)
return net
return net
end
return factory

View File

@ -5,54 +5,56 @@
--]]
local helpers = require("lain.helpers")
local shell = require("awful.util").shell
local wibox = require("wibox")
local string = string
local type = type
local helpers = require('lain.helpers')
local shell = require('awful.util').shell
local wibox = require('wibox')
local string = string
local type = type
-- PulseAudio volume
-- lain.widget.pulse
local function factory(args)
args = args or {}
args = args or {}
local pulse = { widget = args.widget or wibox.widget.textbox(), device = "N/A" }
local timeout = args.timeout or 5
local settings = args.settings or function() end
local pulse = { widget = args.widget or wibox.widget.textbox(), device = 'N/A' }
local timeout = args.timeout or 5
local settings = args.settings or function() end
pulse.devicetype = args.devicetype or "sink"
pulse.cmd = args.cmd or "pacmd list-" .. pulse.devicetype .. "s | sed -n -e '/*/,$!d' -e '/index/p' -e '/base volume/d' -e '/volume:/p' -e '/muted:/p' -e '/device\\.string/p'"
pulse.devicetype = args.devicetype or 'sink'
pulse.cmd = args.cmd
or 'pacmd list-'
.. pulse.devicetype
.. "s | sed -n -e '/*/,$!d' -e '/index/p' -e '/base volume/d' -e '/volume:/p' -e '/muted:/p' -e '/device\\.string/p'"
function pulse.update()
helpers.async({ shell, "-c", type(pulse.cmd) == "string" and pulse.cmd or pulse.cmd() },
function(s)
volume_now = {
index = string.match(s, "index: (%S+)") or "N/A",
device = string.match(s, "device.string = \"(%S+)\"") or "N/A",
muted = string.match(s, "muted: (%S+)") or "N/A"
}
function pulse.update()
helpers.async({ shell, '-c', type(pulse.cmd) == 'string' and pulse.cmd or pulse.cmd() }, function(s)
volume_now = {
index = string.match(s, 'index: (%S+)') or 'N/A',
device = string.match(s, 'device.string = "(%S+)"') or 'N/A',
muted = string.match(s, 'muted: (%S+)') or 'N/A',
}
pulse.device = volume_now.index
pulse.device = volume_now.index
local ch = 1
volume_now.channel = {}
for v in string.gmatch(s, ":.-(%d+)%%") do
volume_now.channel[ch] = v
ch = ch + 1
end
local ch = 1
volume_now.channel = {}
for v in string.gmatch(s, ':.-(%d+)%%') do
volume_now.channel[ch] = v
ch = ch + 1
end
volume_now.left = volume_now.channel[1] or "N/A"
volume_now.right = volume_now.channel[2] or "N/A"
volume_now.left = volume_now.channel[1] or 'N/A'
volume_now.right = volume_now.channel[2] or 'N/A'
widget = pulse.widget
settings()
end)
end
widget = pulse.widget
settings()
end)
end
helpers.newtimer("pulse", timeout, pulse.update)
helpers.newtimer('pulse', timeout, pulse.update)
return pulse
return pulse
end
return factory

View File

@ -6,170 +6,180 @@
--]]
local helpers = require("lain.helpers")
local awful = require("awful")
local naughty = require("naughty")
local wibox = require("wibox")
local math = math
local string = string
local type = type
local helpers = require('lain.helpers')
local awful = require('awful')
local naughty = require('naughty')
local wibox = require('wibox')
local math = math
local string = string
local type = type
local tonumber = tonumber
-- PulseAudio volume bar
-- lain.widget.pulsebar
local function factory(args)
local pulsebar = {
colors = {
background = "#000000",
mute_background = "#000000",
mute = "#EB8F8F",
unmute = "#A4CE8A"
},
local pulsebar = {
colors = {
background = '#000000',
mute_background = '#000000',
mute = '#EB8F8F',
unmute = '#A4CE8A',
},
_current_level = 0,
_mute = "no",
device = "N/A"
}
_current_level = 0,
_mute = 'no',
device = 'N/A',
}
args = args or {}
args = args or {}
local timeout = args.timeout or 5
local settings = args.settings or function() end
local width = args.width or 63
local height = args.height or 1
local margins = args.margins or 1
local paddings = args.paddings or 1
local ticks = args.ticks or false
local ticks_size = args.ticks_size or 7
local tick = args.tick or "|"
local tick_pre = args.tick_pre or "["
local tick_post = args.tick_post or "]"
local tick_none = args.tick_none or " "
local timeout = args.timeout or 5
local settings = args.settings or function() end
local width = args.width or 63
local height = args.height or 1
local margins = args.margins or 1
local paddings = args.paddings or 1
local ticks = args.ticks or false
local ticks_size = args.ticks_size or 7
local tick = args.tick or '|'
local tick_pre = args.tick_pre or '['
local tick_post = args.tick_post or ']'
local tick_none = args.tick_none or ' '
pulsebar.colors = args.colors or pulsebar.colors
pulsebar.followtag = args.followtag or false
pulsebar.notification_preset = args.notification_preset
pulsebar.devicetype = args.devicetype or "sink"
pulsebar.cmd = args.cmd or "pacmd list-" .. pulsebar.devicetype .. "s | sed -n -e '/*/,$!d' -e '/index/p' -e '/base volume/d' -e '/volume:/p' -e '/muted:/p' -e '/device\\.string/p'"
pulsebar.colors = args.colors or pulsebar.colors
pulsebar.followtag = args.followtag or false
pulsebar.notification_preset = args.notification_preset
pulsebar.devicetype = args.devicetype or 'sink'
pulsebar.cmd = args.cmd
or 'pacmd list-'
.. pulsebar.devicetype
.. "s | sed -n -e '/*/,$!d' -e '/index/p' -e '/base volume/d' -e '/volume:/p' -e '/muted:/p' -e '/device\\.string/p'"
if not pulsebar.notification_preset then
pulsebar.notification_preset = {
font = "Monospace 10"
}
end
if not pulsebar.notification_preset then
pulsebar.notification_preset = {
font = 'Monospace 10',
}
end
pulsebar.bar = wibox.widget {
color = pulsebar.colors.unmute,
background_color = pulsebar.colors.background,
forced_height = height,
forced_width = width,
margins = margins,
paddings = paddings,
ticks = ticks,
ticks_size = ticks_size,
widget = wibox.widget.progressbar,
}
pulsebar.bar = wibox.widget({
color = pulsebar.colors.unmute,
background_color = pulsebar.colors.background,
forced_height = height,
forced_width = width,
margins = margins,
paddings = paddings,
ticks = ticks,
ticks_size = ticks_size,
widget = wibox.widget.progressbar,
})
pulsebar.tooltip = awful.tooltip({ objects = { pulsebar.bar } })
pulsebar.tooltip = awful.tooltip({ objects = { pulsebar.bar } })
function pulsebar.update(callback)
helpers.async({ awful.util.shell, "-c", type(pulsebar.cmd) == "string" and pulsebar.cmd or pulsebar.cmd() },
function(s)
volume_now = {
index = string.match(s, "index: (%S+)") or "N/A",
device = string.match(s, "device.string = \"(%S+)\"") or "N/A",
muted = string.match(s, "muted: (%S+)") or "N/A"
}
function pulsebar.update(callback)
helpers.async(
{ awful.util.shell, '-c', type(pulsebar.cmd) == 'string' and pulsebar.cmd or pulsebar.cmd() },
function(s)
volume_now = {
index = string.match(s, 'index: (%S+)') or 'N/A',
device = string.match(s, 'device.string = "(%S+)"') or 'N/A',
muted = string.match(s, 'muted: (%S+)') or 'N/A',
}
pulsebar.device = volume_now.index
pulsebar.device = volume_now.index
local ch = 1
volume_now.channel = {}
for v in string.gmatch(s, ":.-(%d+)%%") do
volume_now.channel[ch] = v
ch = ch + 1
end
local ch = 1
volume_now.channel = {}
for v in string.gmatch(s, ':.-(%d+)%%') do
volume_now.channel[ch] = v
ch = ch + 1
end
volume_now.left = volume_now.channel[1] or "N/A"
volume_now.right = volume_now.channel[2] or "N/A"
volume_now.left = volume_now.channel[1] or 'N/A'
volume_now.right = volume_now.channel[2] or 'N/A'
local volu = volume_now.left
local mute = volume_now.muted
local volu = volume_now.left
local mute = volume_now.muted
if volu:match("N/A") or mute:match("N/A") then return end
if volu:match('N/A') or mute:match('N/A') then
return
end
if volu ~= pulsebar._current_level or mute ~= pulsebar._mute then
pulsebar._current_level = tonumber(volu)
pulsebar.bar:set_value(pulsebar._current_level / 100)
if pulsebar._current_level == 0 or mute == "yes" then
pulsebar._mute = mute
pulsebar.tooltip:set_text ("[muted]")
pulsebar.bar.color = pulsebar.colors.mute
pulsebar.bar.background_color = pulsebar.colors.mute_background
else
pulsebar._mute = "no"
pulsebar.tooltip:set_text(string.format("%s %s: %s", pulsebar.devicetype, pulsebar.device, volu))
pulsebar.bar.color = pulsebar.colors.unmute
pulsebar.bar.background_color = pulsebar.colors.background
end
if volu ~= pulsebar._current_level or mute ~= pulsebar._mute then
pulsebar._current_level = tonumber(volu)
pulsebar.bar:set_value(pulsebar._current_level / 100)
if pulsebar._current_level == 0 or mute == 'yes' then
pulsebar._mute = mute
pulsebar.tooltip:set_text('[muted]')
pulsebar.bar.color = pulsebar.colors.mute
pulsebar.bar.background_color = pulsebar.colors.mute_background
else
pulsebar._mute = 'no'
pulsebar.tooltip:set_text(
string.format('%s %s: %s', pulsebar.devicetype, pulsebar.device, volu)
)
pulsebar.bar.color = pulsebar.colors.unmute
pulsebar.bar.background_color = pulsebar.colors.background
end
settings()
settings()
if type(callback) == "function" then callback() end
end
end)
end
if type(callback) == 'function' then
callback()
end
end
end
)
end
function pulsebar.notify()
pulsebar.update(function()
local preset = pulsebar.notification_preset
function pulsebar.notify()
pulsebar.update(function()
local preset = pulsebar.notification_preset
preset.title = string.format("%s %s - %s%%", pulsebar.devicetype, pulsebar.device, pulsebar._current_level)
preset.title = string.format('%s %s - %s%%', pulsebar.devicetype, pulsebar.device, pulsebar._current_level)
if pulsebar._mute == "yes" then
preset.title = preset.title .. " muted"
end
if pulsebar._mute == 'yes' then
preset.title = preset.title .. ' muted'
end
-- tot is the maximum number of ticks to display in the notification
-- fallback: default horizontal wibox height
local wib, tot = awful.screen.focused().mywibox, 20
-- tot is the maximum number of ticks to display in the notification
-- fallback: default horizontal wibox height
local wib, tot = awful.screen.focused().mywibox, 20
-- if we can grab mywibox, tot is defined as its height if
-- horizontal, or width otherwise
if wib then
if wib.position == "left" or wib.position == "right" then
tot = wib.width
else
tot = wib.height
end
end
-- if we can grab mywibox, tot is defined as its height if
-- horizontal, or width otherwise
if wib then
if wib.position == 'left' or wib.position == 'right' then
tot = wib.width
else
tot = wib.height
end
end
local int = math.modf((pulsebar._current_level / 100) * tot)
preset.text = string.format(
"%s%s%s%s",
tick_pre,
string.rep(tick, int),
string.rep(tick_none, tot - int),
tick_post
)
local int = math.modf((pulsebar._current_level / 100) * tot)
preset.text =
string.format('%s%s%s%s', tick_pre, string.rep(tick, int), string.rep(tick_none, tot - int), tick_post)
if pulsebar.followtag then preset.screen = awful.screen.focused() end
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
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-%s", pulsebar.devicetype, pulsebar.device), timeout, pulsebar.update)
helpers.newtimer(string.format('pulsebar-%s-%s', pulsebar.devicetype, pulsebar.device), timeout, pulsebar.update)
return pulsebar
return pulsebar
end
return factory

View File

@ -6,34 +6,34 @@
--]]
local helpers = require("lain.helpers")
local wibox = require("wibox")
local helpers = require('lain.helpers')
local wibox = require('wibox')
local open, match = io.open, string.match
-- System load
-- lain.widget.sysload
local function factory(args)
args = args or {}
args = args or {}
local sysload = { widget = args.widget or wibox.widget.textbox() }
local timeout = args.timeout or 2
local settings = args.settings or function() end
local sysload = { widget = args.widget or wibox.widget.textbox() }
local timeout = args.timeout or 2
local settings = args.settings or function() end
function sysload.update()
local f = open("/proc/loadavg")
local ret = f:read("*all")
f:close()
function sysload.update()
local f = open('/proc/loadavg')
local ret = f:read('*all')
f:close()
load_1, load_5, load_15 = match(ret, "([^%s]+) ([^%s]+) ([^%s]+)")
load_1, load_5, load_15 = match(ret, '([^%s]+) ([^%s]+) ([^%s]+)')
widget = sysload.widget
settings()
end
widget = sysload.widget
settings()
end
helpers.newtimer("sysload", timeout, sysload.update)
helpers.newtimer('sysload', timeout, sysload.update)
return sysload
return sysload
end
return factory

View File

@ -5,46 +5,46 @@
--]]
local helpers = require("lain.helpers")
local wibox = require("wibox")
local helpers = require('lain.helpers')
local wibox = require('wibox')
local tonumber = tonumber
-- {thermal,core} temperature info
-- lain.widget.temp
local function factory(args)
args = args or {}
args = args or {}
local temp = { widget = args.widget or wibox.widget.textbox() }
local timeout = args.timeout or 30
local tempfile = args.tempfile or "/sys/devices/virtual/thermal/thermal_zone0/temp"
local format = args.format or "%.1f"
local settings = args.settings or function() end
local temp = { widget = args.widget or wibox.widget.textbox() }
local timeout = args.timeout or 30
local tempfile = args.tempfile or '/sys/devices/virtual/thermal/thermal_zone0/temp'
local format = args.format or '%.1f'
local settings = args.settings or function() end
function temp.update()
helpers.async({"find", "/sys/devices", "-type", "f", "-name", "*temp*"}, function(f)
temp_now = {}
local temp_fl, temp_value
for t in f:gmatch("[^\n]+") do
temp_fl = helpers.first_line(t)
if temp_fl then
temp_value = tonumber(temp_fl)
temp_now[t] = temp_value and temp_value/1e3 or temp_fl
end
end
if temp_now[tempfile] then
coretemp_now = string.format(format, temp_now[tempfile])
else
coretemp_now = "N/A"
end
widget = temp.widget
settings()
end)
end
function temp.update()
helpers.async({ 'find', '/sys/devices', '-type', 'f', '-name', '*temp*' }, function(f)
temp_now = {}
local temp_fl, temp_value
for t in f:gmatch('[^\n]+') do
temp_fl = helpers.first_line(t)
if temp_fl then
temp_value = tonumber(temp_fl)
temp_now[t] = temp_value and temp_value / 1e3 or temp_fl
end
end
if temp_now[tempfile] then
coretemp_now = string.format(format, temp_now[tempfile])
else
coretemp_now = 'N/A'
end
widget = temp.widget
settings()
end)
end
helpers.newtimer("thermal", timeout, temp.update)
helpers.newtimer('thermal', timeout, temp.update)
return temp
return temp
end
return factory

View File

@ -5,15 +5,15 @@
--]]
local helpers = require("lain.helpers")
local json = require("lain.util").dkjson
local focused = require("awful.screen").focused
local naughty = require("naughty")
local wibox = require("wibox")
local math = math
local os = os
local string = string
local type = type
local helpers = require('lain.helpers')
local json = require('lain.util').dkjson
local focused = require('awful.screen').focused
local naughty = require('naughty')
local wibox = require('wibox')
local math = math
local os = os
local string = string
local type = type
local tonumber = tonumber
-- OpenWeatherMap
@ -21,126 +21,131 @@ local tonumber = tonumber
-- lain.widget.weather
local function factory(args)
args = args or {}
args = args or {}
local weather = { widget = args.widget or wibox.widget.textbox() }
local APPID = args.APPID -- mandatory
local timeout = args.timeout or 60 * 15 -- 15 min
local current_call = args.current_call or "curl -s 'https://api.openweathermap.org/data/2.5/weather?id=%s&units=%s&lang=%s&APPID=%s'"
local forecast_call = args.forecast_call or "curl -s 'https://api.openweathermap.org/data/2.5/forecast?id=%s&units=%s&lang=%s&APPID=%s'"
local city_id = args.city_id or 0 -- placeholder
local units = args.units or "metric"
local lang = args.lang or "en"
local cnt = args.cnt or 5
local icons_path = args.icons_path or helpers.icons_dir .. "openweathermap/"
local notification_preset = args.notification_preset or {}
local notification_text_fun = args.notification_text_fun or
function (wn)
local day = os.date("%a %d", wn["dt"])
local temp = math.floor(wn["main"]["temp"])
local desc = wn["weather"][1]["description"]
return string.format("<b>%s</b>: %s, %d ", day, desc, temp)
end
local weather_na_markup = args.weather_na_markup or " N/A "
local followtag = args.followtag or false
local showpopup = args.showpopup or "on"
local settings = args.settings or function() end
local weather = { widget = args.widget or wibox.widget.textbox() }
local APPID = args.APPID -- mandatory
local timeout = args.timeout or 60 * 15 -- 15 min
local current_call = args.current_call
or "curl -s 'https://api.openweathermap.org/data/2.5/weather?id=%s&units=%s&lang=%s&APPID=%s'"
local forecast_call = args.forecast_call
or "curl -s 'https://api.openweathermap.org/data/2.5/forecast?id=%s&units=%s&lang=%s&APPID=%s'"
local city_id = args.city_id or 0 -- placeholder
local units = args.units or 'metric'
local lang = args.lang or 'en'
local cnt = args.cnt or 5
local icons_path = args.icons_path or helpers.icons_dir .. 'openweathermap/'
local notification_preset = args.notification_preset or {}
local notification_text_fun = args.notification_text_fun
or function(wn)
local day = os.date('%a %d', wn['dt'])
local temp = math.floor(wn['main']['temp'])
local desc = wn['weather'][1]['description']
return string.format('<b>%s</b>: %s, %d ', day, desc, temp)
end
local weather_na_markup = args.weather_na_markup or ' N/A '
local followtag = args.followtag or false
local showpopup = args.showpopup or 'on'
local settings = args.settings or function() end
weather.widget:set_markup(weather_na_markup)
weather.icon_path = icons_path .. "na.png"
weather.icon = wibox.widget.imagebox(weather.icon_path)
weather.widget:set_markup(weather_na_markup)
weather.icon_path = icons_path .. 'na.png'
weather.icon = wibox.widget.imagebox(weather.icon_path)
function weather.show(seconds)
weather.hide()
function weather.show(seconds)
weather.hide()
if followtag then
notification_preset.screen = focused()
end
if followtag then
notification_preset.screen = focused()
end
if not weather.notification_text then
weather.update()
weather.forecast_update()
end
if not weather.notification_text then
weather.update()
weather.forecast_update()
end
weather.notification = naughty.notify {
preset = notification_preset,
text = weather.notification_text,
icon = weather.icon_path,
timeout = type(seconds) == "number" and seconds or notification_preset.timeout
}
end
weather.notification = naughty.notify({
preset = notification_preset,
text = weather.notification_text,
icon = weather.icon_path,
timeout = type(seconds) == 'number' and seconds or notification_preset.timeout,
})
end
function weather.hide()
if weather.notification then
naughty.destroy(weather.notification)
weather.notification = nil
end
end
function weather.hide()
if weather.notification then
naughty.destroy(weather.notification)
weather.notification = nil
end
end
function weather.attach(obj)
obj:connect_signal("mouse::enter", function()
weather.show(0)
end)
obj:connect_signal("mouse::leave", function()
weather.hide()
end)
end
function weather.attach(obj)
obj:connect_signal('mouse::enter', function()
weather.show(0)
end)
obj:connect_signal('mouse::leave', function()
weather.hide()
end)
end
function weather.forecast_update()
local cmd = string.format(forecast_call, city_id, units, lang, APPID)
helpers.async(cmd, function(f)
local err
weather_now, _, err = json.decode(f, 1, nil)
function weather.forecast_update()
local cmd = string.format(forecast_call, city_id, units, lang, APPID)
helpers.async(cmd, function(f)
local err
weather_now, _, err = json.decode(f, 1, nil)
if not err and type(weather_now) == "table" and tonumber(weather_now["cod"]) == 200 then
weather.notification_text = ""
for i = 1, weather_now["cnt"], math.floor(weather_now["cnt"] / cnt) do
weather.notification_text = weather.notification_text ..
notification_text_fun(weather_now["list"][i])
if i < weather_now["cnt"] then
weather.notification_text = weather.notification_text .. "\n"
end
end
end
end)
end
if not err and type(weather_now) == 'table' and tonumber(weather_now['cod']) == 200 then
weather.notification_text = ''
for i = 1, weather_now['cnt'], math.floor(weather_now['cnt'] / cnt) do
weather.notification_text = weather.notification_text
.. notification_text_fun(weather_now['list'][i])
if i < weather_now['cnt'] then
weather.notification_text = weather.notification_text .. '\n'
end
end
end
end)
end
function weather.update()
local cmd = string.format(current_call, city_id, units, lang, APPID)
helpers.async(cmd, function(f)
local err
weather_now, _, err = json.decode(f, 1, nil)
function weather.update()
local cmd = string.format(current_call, city_id, units, lang, APPID)
helpers.async(cmd, function(f)
local err
weather_now, _, err = json.decode(f, 1, nil)
if not err and type(weather_now) == "table" and tonumber(weather_now["cod"]) == 200 then
local sunrise = tonumber(weather_now["sys"]["sunrise"])
local sunset = tonumber(weather_now["sys"]["sunset"])
local icon = weather_now["weather"][1]["icon"]
local loc_now = os.time()
if not err and type(weather_now) == 'table' and tonumber(weather_now['cod']) == 200 then
local sunrise = tonumber(weather_now['sys']['sunrise'])
local sunset = tonumber(weather_now['sys']['sunset'])
local icon = weather_now['weather'][1]['icon']
local loc_now = os.time()
if sunrise <= loc_now and loc_now <= sunset then
icon = string.gsub(icon, "n", "d")
else
icon = string.gsub(icon, "d", "n")
end
if sunrise <= loc_now and loc_now <= sunset then
icon = string.gsub(icon, 'n', 'd')
else
icon = string.gsub(icon, 'd', 'n')
end
weather.icon_path = icons_path .. icon .. ".png"
widget = weather.widget
settings()
else
weather.icon_path = icons_path .. "na.png"
weather.widget:set_markup(weather_na_markup)
end
weather.icon_path = icons_path .. icon .. '.png'
widget = weather.widget
settings()
else
weather.icon_path = icons_path .. 'na.png'
weather.widget:set_markup(weather_na_markup)
end
weather.icon:set_image(weather.icon_path)
end)
end
weather.icon:set_image(weather.icon_path)
end)
end
if showpopup == "on" then weather.attach(weather.widget) end
if showpopup == 'on' then
weather.attach(weather.widget)
end
weather.timer = helpers.newtimer("weather-" .. city_id, timeout, weather.update, false, true)
weather.timer_forecast = helpers.newtimer("weather_forecast-" .. city_id, timeout, weather.forecast_update, false, true)
weather.timer = helpers.newtimer('weather-' .. city_id, timeout, weather.update, false, true)
weather.timer_forecast =
helpers.newtimer('weather_forecast-' .. city_id, timeout, weather.forecast_update, false, true)
return weather
return weather
end
return factory

View File

@ -10,13 +10,29 @@ local lain = require('lain')
local awful = require('awful')
local wibox = require('wibox')
local dpi = require('beautiful.xresources').apply_dpi
local naughty = require('naughty')
local settings = (function()
local status, settings = pcall(function()
return dofile(os.getenv('HOME') .. '/.config/awesome/settings.lua')
end)
if status then
return settings
else
naughty.notify({
preset = naughty.config.presets.critical,
title = 'Error while parsing settings!',
text = settings,
})
return {}
end
end)()
local os = os
local my_table = awful.util.table or gears.table -- 4.{0,1} compatibility
local theme = {}
theme.confdir = os.getenv('HOME') .. '/.config/awesome/'
theme.wallpaper = os.getenv('HOME') .. '/.config/awesome/wall.png'
theme.wallpaper = os.getenv('HOME') .. '/.config/awesome/' .. (settings['wallpaper'] or 'wall.png')
theme.font = 'Terminus 8'
theme.menu_bg_normal = '#000000'
theme.menu_bg_focus = '#000000'
@ -146,29 +162,39 @@ local cpu = lain.widget.cpu({
end,
})
local optional = {}
-- Coretemp
--[[local tempicon = wibox.widget.imagebox(theme.widget_temp)
local temp = lain.widget.temp({
settings = function()
widget:set_markup(markup.fontfg(theme.font, "#f1af5f", coretemp_now .. "°C "))
end
})]]
--
if settings['show_temp'] == true then
local tempicon = wibox.widget.imagebox(theme.widget_temp)
local temp = lain.widget.temp({
settings = function()
-- luacheck: globals widget coretemp_now
widget:set_markup(markup.fontfg(theme.font, '#f1af5f', coretemp_now .. '°C '))
end,
})
table.insert(optional, tempicon)
table.insert(optional, temp)
end
-- Battery
--[[local baticon = wibox.widget.imagebox(theme.widget_batt)
local bat = lain.widget.bat({
settings = function()
local perc = bat_now.perc ~= "N/A" and bat_now.perc .. "%" or bat_now.perc
if settings['show_battery'] == true then
local baticon = wibox.widget.imagebox(theme.widget_batt)
local bat = lain.widget.bat({
settings = function()
-- luacheck: globals widget bat_now
local perc = bat_now.perc ~= 'N/A' and bat_now.perc .. '%' or bat_now.perc
if bat_now.ac_status == 1 then
perc = perc .. " plug"
end
if bat_now.ac_status == 1 then
perc = perc .. ' plug'
end
widget:set_markup(markup.fontfg(theme.font, theme.fg_normal, perc .. " "))
end
})]]
--
widget:set_markup(markup.fontfg(theme.font, theme.fg_normal, perc .. ' '))
end,
})
table.insert(optional, baticon)
table.insert(optional, bat)
end
-- ALSA volume
local volicon = wibox.widget.imagebox(theme.widget_vol)
@ -331,44 +357,43 @@ function theme.at_screen_connect(s)
},
s.mytasklist, -- Middle widget
--nil,
{ -- Right widgets
layout = wibox.layout.fixed.horizontal,
wibox.widget.systray(),
--mailicon,
--theme.mail.widget,
{
gears.table.join(
{ -- Right widgets
layout = wibox.layout.fixed.horizontal,
wibox.widget.systray(),
--mailicon,
--theme.mail.widget,
{
{
layout = wibox.layout.fixed.horizontal,
netdownicon,
netdowninfo,
netupicon,
netupinfo.widget,
{
layout = wibox.layout.fixed.horizontal,
netdownicon,
netdowninfo,
netupicon,
netupinfo.widget,
},
halign = 'right',
widget = wibox.container.place,
},
halign = 'right',
widget = wibox.container.place,
width = 120,
strategy = 'exact',
layout = wibox.layout.constraint,
},
width = 120,
strategy = 'exact',
layout = wibox.layout.constraint,
volicon,
theme.volume.widget,
memicon,
memory.widget,
cpuicon,
cpu.widget,
fsicon,
theme.fs.widget,
},
volicon,
theme.volume.widget,
memicon,
memory.widget,
cpuicon,
cpu.widget,
fsicon,
theme.fs.widget,
--weathericon,
--theme.weather.widget,
--tempicon,
--temp.widget,
--baticon,
--bat.widget,
clockicon,
mytextclock,
},
optional,
{
clockicon,
mytextclock,
}
),
})
end

BIN
awesome/wall2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 MiB