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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,275 +8,305 @@
--]] --]]
local math = math local math = math
local screen = screen local screen = screen
local tonumber = tonumber local tonumber = tonumber
local termfair = { name = "termfair" } local termfair = { name = 'termfair' }
termfair.center = { name = "centerfair" } termfair.center = { name = 'centerfair' }
termfair.stable = { name = "stablefair" } termfair.stable = { name = 'stablefair' }
local function do_fair(p, orientation) local function do_fair(p, orientation)
local t = p.tag or screen[p.screen].selected_tag local t = p.tag or screen[p.screen].selected_tag
local wa = p.workarea local wa = p.workarea
local cls = p.clients 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. -- How many vertical columns? Read from nmaster on the tag.
local num_x = tonumber(termfair.nmaster) or t.master_count local num_x = tonumber(termfair.nmaster) or t.master_count
local ncol = tonumber(termfair.ncol) or t.column_count local ncol = tonumber(termfair.ncol) or t.column_count
if num_x <= 2 then num_x = 2 end if num_x <= 2 then
if ncol <= 1 then ncol = 1 end num_x = 2
local width = math.floor(wa.width/num_x) end
if ncol <= 1 then
ncol = 1
end
local width = math.floor(wa.width / num_x)
if orientation == "west" then if orientation == 'west' then
-- Layout with fixed number of vertical columns (read from nmaster). -- Layout with fixed number of vertical columns (read from nmaster).
-- New windows align from left to right. When a row is full, a new -- New windows align from left to right. When a row is full, a new
-- one above it is created. Like this: -- one above it is created. Like this:
-- (1) (2) (3) -- (1) (2) (3)
-- +---+---+---+ +---+---+---+ +---+---+---+ -- +---+---+---+ +---+---+---+ +---+---+---+
-- | | | | | | | | | | | | -- | | | | | | | | | | | |
-- | 1 | | | -> | 1 | 2 | | -> | 1 | 2 | 3 | -> -- | 1 | | | -> | 1 | 2 | | -> | 1 | 2 | 3 | ->
-- | | | | | | | | | | | | -- | | | | | | | | | | | |
-- +---+---+---+ +---+---+---+ +---+---+---+ -- +---+---+---+ +---+---+---+ +---+---+---+
-- (4) (5) (6) -- (4) (5) (6)
-- +---+---+---+ +---+---+---+ +---+---+---+ -- +---+---+---+ +---+---+---+ +---+---+---+
-- | 1 | | | | 1 | 2 | | | 1 | 2 | 3 | -- | 1 | | | | 1 | 2 | | | 1 | 2 | 3 |
-- +---+---+---+ -> +---+---+---+ -> +---+---+---+ -- +---+---+---+ -> +---+---+---+ -> +---+---+---+
-- | 2 | 3 | 4 | | 3 | 4 | 5 | | 4 | 5 | 6 | -- | 2 | 3 | 4 | | 3 | 4 | 5 | | 4 | 5 | 6 |
-- +---+---+---+ +---+---+---+ +---+---+---+ -- +---+---+---+ +---+---+---+ +---+---+---+
local num_y = math.max(math.ceil(#cls / num_x), ncol) local num_y = math.max(math.ceil(#cls / num_x), ncol)
local height = math.floor(wa.height/num_y) local height = math.floor(wa.height / num_y)
local cur_num_x = num_x local cur_num_x = num_x
local at_x = 0 local at_x = 0
local at_y = 0 local at_y = 0
local remaining_clients = #cls local remaining_clients = #cls
-- We start the first row. Left-align by limiting the number of -- We start the first row. Left-align by limiting the number of
-- available slots. -- available slots.
if remaining_clients < num_x then if remaining_clients < num_x then
cur_num_x = remaining_clients cur_num_x = remaining_clients
end end
-- Iterate in reversed order. -- Iterate in reversed order.
for i = #cls,1,-1 do for i = #cls, 1, -1 do
-- Get x and y position. -- Get x and y position.
local c = cls[i] local c = cls[i]
local this_x = cur_num_x - at_x - 1 local this_x = cur_num_x - at_x - 1
local this_y = num_y - at_y - 1 local this_y = num_y - at_y - 1
-- Calculate geometry. -- Calculate geometry.
local g = {} local g = {}
if this_x == (num_x - 1) then if this_x == (num_x - 1) then
g.width = wa.width - (num_x - 1)*width g.width = wa.width - (num_x - 1) * width
else else
g.width = width g.width = width
end end
if this_y == (num_y - 1) then if this_y == (num_y - 1) then
g.height = wa.height - (num_y - 1)*height g.height = wa.height - (num_y - 1) * height
else else
g.height = height g.height = height
end end
g.x = wa.x + this_x*width g.x = wa.x + this_x * width
g.y = wa.y + this_y*height g.y = wa.y + this_y * height
if g.width < 1 then g.width = 1 end if g.width < 1 then
if g.height < 1 then g.height = 1 end 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. -- Next grid position.
at_x = at_x + 1 at_x = at_x + 1
if at_x == num_x then if at_x == num_x then
-- Row full, create a new one above it. -- Row full, create a new one above it.
at_x = 0 at_x = 0
at_y = at_y + 1 at_y = at_y + 1
-- We start a new row. Left-align. -- We start a new row. Left-align.
if remaining_clients < num_x then if remaining_clients < num_x then
cur_num_x = remaining_clients cur_num_x = remaining_clients
end end
end end
end end
elseif orientation == "stable" then elseif orientation == 'stable' then
-- Layout with fixed number of vertical columns (read from nmaster). -- Layout with fixed number of vertical columns (read from nmaster).
-- New windows align from left to right. When a row is full, a new -- New windows align from left to right. When a row is full, a new
-- one below it is created. Like this: -- one below it is created. Like this:
-- (1) (2) (3) -- (1) (2) (3)
-- +---+---+---+ +---+---+---+ +---+---+---+ -- +---+---+---+ +---+---+---+ +---+---+---+
-- | | | | | | | | | | | | -- | | | | | | | | | | | |
-- | 1 | | | -> | 1 | 2 | | -> | 1 | 2 | 3 | -> -- | 1 | | | -> | 1 | 2 | | -> | 1 | 2 | 3 | ->
-- | | | | | | | | | | | | -- | | | | | | | | | | | |
-- +---+---+---+ +---+---+---+ +---+---+---+ -- +---+---+---+ +---+---+---+ +---+---+---+
-- (4) (5) (6) -- (4) (5) (6)
-- +---+---+---+ +---+---+---+ +---+---+---+ -- +---+---+---+ +---+---+---+ +---+---+---+
-- | 1 | 2 | 3 | | 1 | 2 | 3 | | 1 | 2 | 3 | -- | 1 | 2 | 3 | | 1 | 2 | 3 | | 1 | 2 | 3 |
-- +---+---+---+ +---+---+---+ +---+---+---+ -- +---+---+---+ +---+---+---+ +---+---+---+
-- | 4 | | | | 4 | 5 | | | 4 | 5 | 6 | -- | 4 | | | | 4 | 5 | | | 4 | 5 | 6 |
-- +---+---+---+ -> +---+---+---+ -> +---+---+---+ -- +---+---+---+ -> +---+---+---+ -> +---+---+---+
local num_y = math.max(math.ceil(#cls / num_x), ncol) local num_y = math.max(math.ceil(#cls / num_x), ncol)
local height = math.floor(wa.height/num_y) local height = math.floor(wa.height / num_y)
for i = #cls,1,-1 do for i = #cls, 1, -1 do
-- Get x and y position. -- Get x and y position.
local c = cls[i] local c = cls[i]
local this_x = (i - 1) % num_x local this_x = (i - 1) % num_x
local this_y = math.floor((i - this_x - 1) / num_x) local this_y = math.floor((i - this_x - 1) / num_x)
-- Calculate geometry. -- Calculate geometry.
local g = {} local g = {}
if this_x == (num_x - 1) then if this_x == (num_x - 1) then
g.width = wa.width - (num_x - 1)*width g.width = wa.width - (num_x - 1) * width
else else
g.width = width g.width = width
end end
if this_y == (num_y - 1) then if this_y == (num_y - 1) then
g.height = wa.height - (num_y - 1)*height g.height = wa.height - (num_y - 1) * height
else else
g.height = height g.height = height
end end
g.x = wa.x + this_x*width g.x = wa.x + this_x * width
g.y = wa.y + this_y*height g.y = wa.y + this_y * height
if g.width < 1 then g.width = 1 end if g.width < 1 then
if g.height < 1 then g.height = 1 end g.width = 1
end
if g.height < 1 then
g.height = 1
end
p.geometries[c] = g p.geometries[c] = g
end end
elseif orientation == "center" then elseif orientation == 'center' then
-- Layout with fixed number of vertical columns (read from nmaster). -- Layout with fixed number of vertical columns (read from nmaster).
-- Cols are centerded until there is nmaster columns, then windows -- Cols are centerded until there is nmaster columns, then windows
-- are stacked in the slave columns, with at most ncol clients per -- are stacked in the slave columns, with at most ncol clients per
-- column if possible. -- column if possible.
-- with nmaster=3 and ncol=1 you'll have -- with nmaster=3 and ncol=1 you'll have
-- (1) (2) (3) -- (1) (2) (3)
-- +---+---+---+ +-+---+---+-+ +---+---+---+ -- +---+---+---+ +-+---+---+-+ +---+---+---+
-- | | | | | | | | | | | | | -- | | | | | | | | | | | | |
-- | | 1 | | -> | | 1 | 2 | | -> | 1 | 2 | 3 | -> -- | | 1 | | -> | | 1 | 2 | | -> | 1 | 2 | 3 | ->
-- | | | | | | | | | | | | | -- | | | | | | | | | | | | |
-- +---+---+---+ +-+---+---+-+ +---+---+---+ -- +---+---+---+ +-+---+---+-+ +---+---+---+
-- (4) (5) -- (4) (5)
-- +---+---+---+ +---+---+---+ -- +---+---+---+ +---+---+---+
-- | | | 3 | | | 2 | 4 | -- | | | 3 | | | 2 | 4 |
-- + 1 + 2 +---+ -> + 1 +---+---+ -- + 1 + 2 +---+ -> + 1 +---+---+
-- | | | 4 | | | 3 | 5 | -- | | | 4 | | | 3 | 5 |
-- +---+---+---+ +---+---+---+ -- +---+---+---+ +---+---+---+
if #cls < num_x then if #cls < num_x then
-- Less clients than the number of columns, let's center it! -- Less clients than the number of columns, let's center it!
local offset_x = wa.x + (wa.width - #cls*width) / 2 local offset_x = wa.x + (wa.width - #cls * width) / 2
for i = 1, #cls do for i = 1, #cls do
local g = { y = wa.y } local g = { y = wa.y }
g.width = width g.width = width
g.height = wa.height g.height = wa.height
if g.width < 1 then g.width = 1 end if g.width < 1 then
if g.height < 1 then g.height = 1 end g.width = 1
g.x = offset_x + (i - 1) * width end
p.geometries[cls[i]] = g if g.height < 1 then
end g.height = 1
else end
-- More clients than the number of columns, let's arrange it! g.x = offset_x + (i - 1) * width
-- Master client deserves a special treatement p.geometries[cls[i]] = g
local g = {} end
g.width = wa.width - (num_x - 1)*width else
g.height = wa.height -- More clients than the number of columns, let's arrange it!
if g.width < 1 then g.width = 1 end -- Master client deserves a special treatement
if g.height < 1 then g.height = 1 end local g = {}
g.x = wa.x g.width = wa.width - (num_x - 1) * width
g.y = wa.y g.height = wa.height
p.geometries[cls[1]] = g 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 -- Compute distribution of clients among columns
local num_y = {} local num_y = {}
local remaining_clients = #cls-1 local remaining_clients = #cls - 1
local ncol_min = math.ceil(remaining_clients/(num_x-1)) local ncol_min = math.ceil(remaining_clients / (num_x - 1))
if ncol >= ncol_min then if ncol >= ncol_min then
for i = (num_x-1), 1, -1 do for i = (num_x - 1), 1, -1 do
if (remaining_clients-i+1) < ncol then if (remaining_clients - i + 1) < ncol then
num_y[i] = remaining_clients-i + 1 num_y[i] = remaining_clients - i + 1
else else
num_y[i] = ncol num_y[i] = ncol
end end
remaining_clients = remaining_clients - num_y[i] remaining_clients = remaining_clients - num_y[i]
end end
else else
local rem = remaining_clients % (num_x-1) local rem = remaining_clients % (num_x - 1)
if rem == 0 then if rem == 0 then
for i = 1, num_x-1 do for i = 1, num_x - 1 do
num_y[i] = ncol_min num_y[i] = ncol_min
end end
else else
for i = 1, num_x-1 do for i = 1, num_x - 1 do
num_y[i] = ncol_min - 1 num_y[i] = ncol_min - 1
end end
for i = 0, rem-1 do for i = 0, rem - 1 do
num_y[num_x-1-i] = num_y[num_x-1-i] + 1 num_y[num_x - 1 - i] = num_y[num_x - 1 - i] + 1
end end
end end
end end
-- Compute geometry of the other clients -- Compute geometry of the other clients
local nclient = 2 -- we start with the 2nd client local nclient = 2 -- we start with the 2nd client
local wx = g.x + g.width local wx = g.x + g.width
for i = 1, (num_x-1) do for i = 1, (num_x - 1) do
local height = math.floor(wa.height / num_y[i]) local height = math.floor(wa.height / num_y[i])
local wy = wa.y local wy = wa.y
for _ = 0, (num_y[i]-2) do for _ = 0, (num_y[i] - 2) do
g = {} g = {}
g.x = wx g.x = wx
g.y = wy g.y = wy
g.height = height g.height = height
g.width = width g.width = width
if g.width < 1 then g.width = 1 end if g.width < 1 then
if g.height < 1 then g.height = 1 end g.width = 1
p.geometries[cls[nclient]] = g end
nclient = nclient + 1 if g.height < 1 then
wy = wy + height g.height = 1
end end
g = {} p.geometries[cls[nclient]] = g
g.x = wx nclient = nclient + 1
g.y = wy wy = wy + height
g.height = wa.height - (num_y[i] - 1)*height end
g.width = width g = {}
if g.width < 1 then g.width = 1 end g.x = wx
if g.height < 1 then g.height = 1 end g.y = wy
p.geometries[cls[nclient]] = g g.height = wa.height - (num_y[i] - 1) * height
nclient = nclient + 1 g.width = width
wx = wx + width if g.width < 1 then
end g.width = 1
end end
end if g.height < 1 then
g.height = 1
end
p.geometries[cls[nclient]] = g
nclient = nclient + 1
wx = wx + width
end
end
end
end end
function termfair.center.arrange(p) function termfair.center.arrange(p)
return do_fair(p, "center") return do_fair(p, 'center')
end end
function termfair.stable.arrange(p) function termfair.stable.arrange(p)
return do_fair(p, "stable") return do_fair(p, 'stable')
end end
function termfair.arrange(p) function termfair.arrange(p)
return do_fair(p, "west") return do_fair(p, 'west')
end end
return termfair return termfair

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -15,52 +15,78 @@ local setmetatable = setmetatable
local markup = { fg = {}, bg = {} } local markup = { fg = {}, bg = {} }
-- Convenience tags -- Convenience tags
function markup.bold(text) return format("<b>%s</b>", text) end function markup.bold(text)
function markup.italic(text) return format("<i>%s</i>", text) end return format('<b>%s</b>', text)
function markup.strike(text) return format("<s>%s</s>", text) end end
function markup.underline(text) return format("<u>%s</u>", text) end function markup.italic(text)
function markup.monospace(text) return format("<tt>%s</tt>", text) end return format('<i>%s</i>', text)
function markup.big(text) return format("<big>%s</big>", text) end end
function markup.small(text) return format("<small>%s</small>", 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 -- Set the font
function markup.font(font, text) function markup.font(font, text)
return format("<span font='%s'>%s</span>", font, text) return format("<span font='%s'>%s</span>", font, text)
end end
-- Set the foreground -- Set the foreground
function markup.fg.color(color, text) 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 end
-- Set the background -- Set the background
function markup.bg.color(color, text) 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 end
-- Set foreground and background -- Set foreground and background
function markup.color(fg, bg, text) 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 end
-- Set font and foreground -- Set font and foreground
function markup.fontfg(font, fg, text) 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 end
-- Set font and background -- Set font and background
function markup.fontbg(font, bg, text) 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 end
-- Set font, foreground and background -- Set font, foreground and background
function markup.fontcolor(font, fg, bg, text) 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 end
-- link markup.{fg,bg}(...) calls to markup.{fg,bg}.color(...) -- link markup.{fg,bg}(...) calls to markup.{fg,bg}.color(...)
setmetatable(markup.fg, { __call = function(_, ...) return markup.fg.color(...) end }) setmetatable(markup.fg, {
setmetatable(markup.bg, { __call = function(_, ...) return markup.bg.color(...) end }) __call = function(_, ...)
return markup.fg.color(...)
end,
})
setmetatable(markup.bg, {
__call = function(_, ...)
return markup.bg.color(...)
end,
})
-- link markup(...) calls to markup.fg.color(...) -- 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 -- Menu iterator with Naughty notifications
-- lain.util.menu_iterator -- lain.util.menu_iterator
local naughty = require("naughty") local naughty = require('naughty')
local helpers = require("lain.helpers") local helpers = require('lain.helpers')
local atable = require("awful.util").table local atable = require('awful.util').table
local assert = assert local assert = assert
local pairs = pairs local pairs = pairs
local tconcat = table.concat local tconcat = table.concat
local unpack = unpack or table.unpack -- lua 5.1 retro-compatibility local unpack = unpack or table.unpack -- lua 5.1 retro-compatibility
local state = { cid = nil } local state = { cid = nil }
local function naughty_destroy_callback(reason) local function naughty_destroy_callback(reason)
local closed = naughty.notificationClosedReason local closed = naughty.notificationClosedReason
if reason == closed.expired or reason == closed.dismissedByUser then if reason == closed.expired or reason == closed.dismissedByUser then
local actions = state.index and state.menu[state.index - 1][2] local actions = state.index and state.menu[state.index - 1][2]
if actions then if actions then
for _,action in pairs(actions) do for _, action in pairs(actions) do
-- don't try to call nil callbacks -- don't try to call nil callbacks
if action then action() end if action then
end action()
state.index = nil end
end end
end state.index = nil
end
end
end end
-- Iterates over a menu. -- Iterates over a menu.
@ -41,35 +43,35 @@ end
-- * timeout: time to wait before confirming the menu selection -- * timeout: time to wait before confirming the menu selection
-- * icon: icon to display in the notification of the chosen label -- * icon: icon to display in the notification of the chosen label
local function iterate(menu, timeout, icon) local function iterate(menu, timeout, icon)
timeout = timeout or 4 -- default timeout for each menu entry timeout = timeout or 4 -- default timeout for each menu entry
icon = icon or nil -- icon to display on the menu icon = icon or nil -- icon to display on the menu
-- Build the list of choices -- Build the list of choices
if not state.index then if not state.index then
state.menu = menu state.menu = menu
state.index = 1 state.index = 1
end end
-- Select one and display the appropriate notification -- Select one and display the appropriate notification
local label local label
local next = state.menu[state.index] local next = state.menu[state.index]
state.index = state.index + 1 state.index = state.index + 1
if not next then if not next then
label = "Cancel" label = 'Cancel'
state.index = nil state.index = nil
else else
label, _ = unpack(next) label, _ = unpack(next)
end end
state.cid = naughty.notify({ state.cid = naughty.notify({
text = label, text = label,
icon = icon, icon = icon,
timeout = timeout, timeout = timeout,
screen = mouse.screen, screen = mouse.screen,
replaces_id = state.cid, replaces_id = state.cid,
destroy = naughty_destroy_callback destroy = naughty_destroy_callback,
}).id }).id
end end
-- Generates a menu compatible with the first argument of `iterate` function and -- Generates a menu compatible with the first argument of `iterate` function and
@ -95,50 +97,55 @@ end
-- Output: -- Output:
-- * m: menu to be iterated over. -- * m: menu to be iterated over.
local function menu(args) local function menu(args)
local choices = assert(args.choices or args[1]) local choices = assert(args.choices or args[1])
local name = assert(args.name or args[2]) local name = assert(args.name or args[2])
local selected_cb = args.selected_cb local selected_cb = args.selected_cb
local rejected_cb = args.rejected_cb local rejected_cb = args.rejected_cb
local extra_choices = args.extra_choices or {} 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 for _, c in pairs(extra_choices) do
ch_combinations = atable.join(ch_combinations, {{c[1]}}) ch_combinations = atable.join(ch_combinations, { { c[1] } })
end end
local m = {} -- the menu local m = {} -- the menu
for _,c in pairs(ch_combinations) do for _, c in pairs(ch_combinations) do
if #c > 0 then if #c > 0 then
local cbs = {} local cbs = {}
-- selected choices -- selected choices
for _,ch in pairs(c) do for _, ch in pairs(c) do
if atable.hasitem(choices, ch) then if atable.hasitem(choices, ch) then
cbs[#cbs + 1] = selected_cb and function() selected_cb(ch) end or nil cbs[#cbs + 1] = selected_cb and function()
end selected_cb(ch)
end end or nil
end
end
-- rejected choices -- rejected choices
for _,ch in pairs(choices) do for _, ch in pairs(choices) do
if not atable.hasitem(c, ch) and atable.hasitem(choices, ch) then 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 cbs[#cbs + 1] = rejected_cb and function()
end rejected_cb(ch)
end end or nil
end
end
-- add user extra choices (like the choice "None" for example) -- add user extra choices (like the choice "None" for example)
for _,x in pairs(extra_choices) do for _, x in pairs(extra_choices) do
if x[1] == c[1] then if x[1] == c[1] then
cbs[#cbs + 1] = x[2] cbs[#cbs + 1] = x[2]
end end
end end
m[#m + 1] = { name .. ": " .. tconcat(c, " + "), cbs } m[#m + 1] = { name .. ': ' .. tconcat(c, ' + '), cbs }
end end
end end
return m return m
end end
return { iterate = iterate, menu = menu } return { iterate = iterate, menu = menu }

View File

@ -6,12 +6,12 @@
--]] --]]
local awful = require("awful") local awful = require('awful')
local capi = { client = client } local capi = { client = client }
local math = math local math = math
local string = string local string = string
local pairs = pairs local pairs = pairs
local screen = screen local screen = screen
local setmetatable = setmetatable local setmetatable = setmetatable
-- Quake-like Dropdown application spawn -- Quake-like Dropdown application spawn
@ -22,158 +22,182 @@ local quake = {}
-- run into problems with focus. -- run into problems with focus.
function quake:display() 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 -- First, we locate the client
local client = nil local client = nil
local i = 0 local i = 0
for c in awful.client.iterate(function (c) for c in
-- c.name may be changed! awful.client.iterate(function(c)
return c.instance == self.name -- c.name may be changed!
end) return c.instance == self.name
do end)
i = i + 1 do
if i == 1 then i = i + 1
client = c if i == 1 then
else client = c
-- Additional matching clients, let's remove the sticky bit else
-- which may persist between awesome restarts. We don't close -- Additional matching clients, let's remove the sticky bit
-- them as they may be valuable. They will just turn into -- which may persist between awesome restarts. We don't close
-- normal clients. -- them as they may be valuable. They will just turn into
c.sticky = false -- normal clients.
c.ontop = false c.sticky = false
c.above = false c.ontop = false
end c.above = false
end 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 if not client then
-- The client does not exist, we spawn it -- The client does not exist, we spawn it
local cmd = string.format("%s %s %s", self.app, local cmd = string.format('%s %s %s', self.app, string.format(self.argname, self.name), self.extra)
string.format(self.argname, self.name), self.extra) awful.spawn(cmd, { tag = self.screen.selected_tag })
awful.spawn(cmd, { tag = self.screen.selected_tag }) return
return end
end
-- Set geometry -- Set geometry
client.floating = true client.floating = true
client.border_width = self.border client.border_width = self.border
client.size_hints_honor = false client.size_hints_honor = false
local maximized = client.maximized local maximized = client.maximized
local fullscreen = client.fullscreen local fullscreen = client.fullscreen
client:geometry(self.geometry[self.screen.index] or self:compute_size()) client:geometry(self.geometry[self.screen.index] or self:compute_size())
-- Set not sticky and on top -- Set not sticky and on top
client.sticky = false client.sticky = false
client.ontop = true client.ontop = true
client.above = true client.above = true
client.skip_taskbar = true client.skip_taskbar = true
-- Additional user settings -- Additional user settings
if self.settings then self.settings(client) end if self.settings then
self.settings(client)
end
-- Toggle display -- Toggle display
if self.visible then if self.visible then
client.hidden = false client.hidden = false
client.maximized = self.maximized client.maximized = self.maximized
client.fullscreen = self.fullscreen client.fullscreen = self.fullscreen
client:raise() client:raise()
self.last_tag = self.screen.selected_tag self.last_tag = self.screen.selected_tag
client:tags({self.screen.selected_tag}) client:tags({ self.screen.selected_tag })
capi.client.focus = client capi.client.focus = client
else else
self.maximized = maximized self.maximized = maximized
self.fullscreen = fullscreen self.fullscreen = fullscreen
client.maximized = false client.maximized = false
client.fullscreen = false client.fullscreen = false
client.hidden = true client.hidden = true
local ctags = client:tags() local ctags = client:tags()
for j, _ in pairs(ctags) do for j, _ in pairs(ctags) do
ctags[j] = nil ctags[j] = nil
end end
client:tags(ctags) client:tags(ctags)
end end
return client return client
end end
function quake:compute_size() function quake:compute_size()
-- skip if we already have a geometry for this screen -- skip if we already have a geometry for this screen
if not self.geometry[self.screen.index] then if not self.geometry[self.screen.index] then
local geom local geom
if not self.overlap then if not self.overlap then
geom = screen[self.screen.index].workarea geom = screen[self.screen.index].workarea
else else
geom = screen[self.screen.index].geometry geom = screen[self.screen.index].geometry
end end
local width, height = self.width, self.height local width, height = self.width, self.height
if width <= 1 then width = math.floor(geom.width * width) - 2 * self.border end if width <= 1 then
if height <= 1 then height = math.floor(geom.height * height) end width = math.floor(geom.width * width) - 2 * self.border
local x, y end
if self.horiz == "left" then x = geom.x if height <= 1 then
elseif self.horiz == "right" then x = geom.width + geom.x - width height = math.floor(geom.height * height)
else x = geom.x + (geom.width - width)/2 end end
if self.vert == "top" then y = geom.y local x, y
elseif self.vert == "bottom" then y = geom.height + geom.y - height if self.horiz == 'left' then
else y = geom.y + (geom.height - height)/2 end x = geom.x
self.geometry[self.screen.index] = { x = x, y = y, width = width, height = height } elseif self.horiz == 'right' then
end x = geom.width + geom.x - width
return self.geometry[self.screen.index] 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 end
function quake:toggle() function quake:toggle()
if self.followtag then self.screen = awful.screen.focused() end if self.followtag then
local current_tag = self.screen.selected_tag self.screen = awful.screen.focused()
if current_tag and self.last_tag ~= current_tag and self.visible then end
local c=self:display() local current_tag = self.screen.selected_tag
if c then if current_tag and self.last_tag ~= current_tag and self.visible then
c:move_to_tag(current_tag) local c = self:display()
end if c then
else c:move_to_tag(current_tag)
self.visible = not self.visible end
self:display() else
end self.visible = not self.visible
self:display()
end
end end
function quake.new(conf) function quake.new(conf)
conf = conf or {} conf = conf or {}
conf.app = conf.app or "xterm" -- application to spawn conf.app = conf.app or 'xterm' -- application to spawn
conf.name = conf.name or "QuakeDD" -- window name conf.name = conf.name or 'QuakeDD' -- window name
conf.argname = conf.argname or "-name %s" -- how to specify window name conf.argname = conf.argname or '-name %s' -- how to specify window name
conf.extra = conf.extra or "" -- extra arguments conf.extra = conf.extra or '' -- extra arguments
conf.border = conf.border or 1 -- client border width conf.border = conf.border or 1 -- client border width
conf.visible = conf.visible or false -- initially not visible conf.visible = conf.visible or false -- initially not visible
conf.followtag = conf.followtag or false -- spawn on currently focused screen conf.followtag = conf.followtag or false -- spawn on currently focused screen
conf.overlap = conf.overlap or false -- overlap wibox conf.overlap = conf.overlap or false -- overlap wibox
conf.screen = conf.screen or awful.screen.focused() conf.screen = conf.screen or awful.screen.focused()
conf.settings = conf.settings conf.settings = conf.settings
-- If width or height <= 1 this is a proportion of the workspace -- If width or height <= 1 this is a proportion of the workspace
conf.height = conf.height or 0.25 -- height conf.height = conf.height or 0.25 -- height
conf.width = conf.width or 1 -- width conf.width = conf.width or 1 -- width
conf.vert = conf.vert or "top" -- top, bottom or center conf.vert = conf.vert or 'top' -- top, bottom or center
conf.horiz = conf.horiz or "left" -- left, right or center conf.horiz = conf.horiz or 'left' -- left, right or center
conf.geometry = {} -- internal use conf.geometry = {} -- internal use
conf.maximized = false conf.maximized = false
conf.fullscreen = false conf.fullscreen = false
local dropdown = setmetatable(conf, { __index = quake }) local dropdown = setmetatable(conf, { __index = quake })
capi.client.connect_signal("manage", function(c) capi.client.connect_signal('manage', function(c)
if c.instance == dropdown.name and c.screen == dropdown.screen then if c.instance == dropdown.name and c.screen == dropdown.screen then
dropdown:display() dropdown:display()
end end
end) end)
capi.client.connect_signal("unmanage", function(c) capi.client.connect_signal('unmanage', function(c)
if c.instance == dropdown.name and c.screen == dropdown.screen then if c.instance == dropdown.name and c.screen == dropdown.screen then
dropdown.visible = false dropdown.visible = false
end end
end) end)
return dropdown return dropdown
end 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 wibox = require('wibox')
local gears = require("gears") local gears = require('gears')
local beautiful = require("beautiful") local beautiful = require('beautiful')
-- Lain Cairo separators util submodule -- Lain Cairo separators util submodule
-- lain.util.separators -- lain.util.separators
@ -18,99 +18,99 @@ local separators = { height = beautiful.separators_height or 0, width = beautifu
-- Right -- Right
function separators.arrow_right(col1, col2) function separators.arrow_right(col1, col2)
local widget = wibox.widget.base.make_widget() local widget = wibox.widget.base.make_widget()
widget.col1 = col1 widget.col1 = col1
widget.col2 = col2 widget.col2 = col2
widget.fit = function(_, _, _) widget.fit = function(_, _, _)
return separators.width, separators.height return separators.width, separators.height
end end
widget.update = function(_, _) widget.update = function(_, _)
widget.col1 = col1 widget.col1 = col1
widget.col2 = col2 widget.col2 = col2
widget:emit_signal("widget::redraw_needed") widget:emit_signal('widget::redraw_needed')
end end
widget.draw = function(_, _, cr, width, height) widget.draw = function(_, _, cr, width, height)
if widget.col2 ~= "alpha" then if widget.col2 ~= 'alpha' then
cr:set_source_rgba(gears.color.parse_color(widget.col2)) cr:set_source_rgba(gears.color.parse_color(widget.col2))
cr:new_path() cr:new_path()
cr:move_to(0, 0) cr:move_to(0, 0)
cr:line_to(width, height/2) cr:line_to(width, height / 2)
cr:line_to(width, 0) cr:line_to(width, 0)
cr:close_path() cr:close_path()
cr:fill() cr:fill()
cr:new_path() cr:new_path()
cr:move_to(0, height) cr:move_to(0, height)
cr:line_to(width, height/2) cr:line_to(width, height / 2)
cr:line_to(width, height) cr:line_to(width, height)
cr:close_path() cr:close_path()
cr:fill() cr:fill()
end end
if widget.col1 ~= "alpha" then if widget.col1 ~= 'alpha' then
cr:set_source_rgba(gears.color.parse_color(widget.col1)) cr:set_source_rgba(gears.color.parse_color(widget.col1))
cr:new_path() cr:new_path()
cr:move_to(0, 0) cr:move_to(0, 0)
cr:line_to(width, height/2) cr:line_to(width, height / 2)
cr:line_to(0, height) cr:line_to(0, height)
cr:close_path() cr:close_path()
cr:fill() cr:fill()
end end
end end
return widget return widget
end end
-- Left -- Left
function separators.arrow_left(col1, col2) function separators.arrow_left(col1, col2)
local widget = wibox.widget.base.make_widget() local widget = wibox.widget.base.make_widget()
widget.col1 = col1 widget.col1 = col1
widget.col2 = col2 widget.col2 = col2
widget.fit = function(_, _, _) widget.fit = function(_, _, _)
return separators.width, separators.height return separators.width, separators.height
end end
widget.update = function(c1, c2) widget.update = function(c1, c2)
widget.col1 = c1 widget.col1 = c1
widget.col2 = c2 widget.col2 = c2
widget:emit_signal("widget::redraw_needed") widget:emit_signal('widget::redraw_needed')
end end
widget.draw = function(_, _, cr, width, height) widget.draw = function(_, _, cr, width, height)
if widget.col1 ~= "alpha" then if widget.col1 ~= 'alpha' then
cr:set_source_rgba(gears.color.parse_color(widget.col1)) cr:set_source_rgba(gears.color.parse_color(widget.col1))
cr:new_path() cr:new_path()
cr:move_to(width, 0) cr:move_to(width, 0)
cr:line_to(0, height/2) cr:line_to(0, height / 2)
cr:line_to(0, 0) cr:line_to(0, 0)
cr:close_path() cr:close_path()
cr:fill() cr:fill()
cr:new_path() cr:new_path()
cr:move_to(width, height) cr:move_to(width, height)
cr:line_to(0, height/2) cr:line_to(0, height / 2)
cr:line_to(0, height) cr:line_to(0, height)
cr:close_path() cr:close_path()
cr:fill() cr:fill()
end end
if widget.col2 ~= "alpha" then if widget.col2 ~= 'alpha' then
cr:new_path() cr:new_path()
cr:move_to(width, 0) cr:move_to(width, 0)
cr:line_to(0, height/2) cr:line_to(0, height / 2)
cr:line_to(width, height) cr:line_to(width, height)
cr:close_path() cr:close_path()
cr:set_source_rgba(gears.color.parse_color(widget.col2)) cr:set_source_rgba(gears.color.parse_color(widget.col2))
cr:fill() cr:fill()
end end
end end
return widget return widget
end end
-- ]] -- ]]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,34 +6,34 @@
--]] --]]
local helpers = require("lain.helpers") local helpers = require('lain.helpers')
local wibox = require("wibox") local wibox = require('wibox')
local open, match = io.open, string.match local open, match = io.open, string.match
-- System load -- System load
-- lain.widget.sysload -- lain.widget.sysload
local function factory(args) local function factory(args)
args = args or {} args = args or {}
local sysload = { widget = args.widget or wibox.widget.textbox() } local sysload = { widget = args.widget or wibox.widget.textbox() }
local timeout = args.timeout or 2 local timeout = args.timeout or 2
local settings = args.settings or function() end local settings = args.settings or function() end
function sysload.update() function sysload.update()
local f = open("/proc/loadavg") local f = open('/proc/loadavg')
local ret = f:read("*all") local ret = f:read('*all')
f:close() 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 widget = sysload.widget
settings() settings()
end end
helpers.newtimer("sysload", timeout, sysload.update) helpers.newtimer('sysload', timeout, sysload.update)
return sysload return sysload
end end
return factory return factory

View File

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

View File

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

View File

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

BIN
awesome/wall2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 MiB