add layout env edge menu rules signal

This commit is contained in:
vincent 2019-10-19 18:54:31 +02:00
parent a345c86326
commit 8b1c4c1a4a
169 changed files with 19824 additions and 0 deletions

View File

@ -0,0 +1,76 @@
-----------------------------------------------------------------------------------------------------------------------
-- Active screen edges config --
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
local awful = require("awful")
local redflat = require("redflat")
-- Initialize tables and vars for module
-----------------------------------------------------------------------------------------------------------------------
local edges = {}
local switcher = redflat.float.appswitcher
local currenttags = redflat.widget.tasklist.filter.currenttags
local allscreen = redflat.widget.tasklist.filter.allscreen
-- Active screen edges
-----------------------------------------------------------------------------------------------------------------------
function edges:init(args)
args = args or {}
local ew = args.width or 1 -- edge width
local workarea = args.workarea or screen[mouse.screen].workarea
-- edge geometry
local egeometry = {
top = { width = workarea.width - 2 * ew, height = ew , x = ew, y = 0 },
right = { width = ew, height = workarea.height, x = workarea.width - ew, y = 0 },
left = { width = ew, height = workarea.height, x = 0, y = 0 }
}
-- Top
--------------------------------------------------------------------------------
local top = redflat.util.desktop.edge("horizontal")
top.wibox:geometry(egeometry["top"])
top.layout:buttons(awful.util.table.join(
awful.button({}, 1,
function()
if client.focus then client.focus.maximized = not client.focus.maximized end
end
)
))
-- Right
--------------------------------------------------------------------------------
local right = redflat.util.desktop.edge("vertical")
right.wibox:geometry(egeometry["right"])
right.layout:buttons(awful.util.table.join(
awful.button({}, 5, function() awful.tag.viewnext(mouse.screen) end),
awful.button({}, 4, function() awful.tag.viewprev(mouse.screen) end)
))
-- Left
--------------------------------------------------------------------------------
local left = redflat.util.desktop.edge("vertical", { ew, workarea.height - ew })
left.wibox:geometry(egeometry["left"])
left.area[1]:buttons(awful.util.table.join(
awful.button({}, 4, function() switcher:show({ filter = allscreen }) end),
awful.button({}, 5, function() switcher:show({ filter = allscreen, reverse = true }) end)
))
left.area[2]:buttons(awful.util.table.join(
awful.button({}, 9, function() if client.focus then client.focus.minimized = true end end),
awful.button({}, 4, function() switcher:show({ filter = currenttags }) end),
awful.button({}, 5, function() switcher:show({ filter = currenttags, reverse = true }) end)
))
left.wibox:connect_signal("mouse::leave", function() redflat.float.appswitcher:hide() end)
end
-- End
-----------------------------------------------------------------------------------------------------------------------
return edges

View File

@ -0,0 +1,100 @@
-----------------------------------------------------------------------------------------------------------------------
-- Environment config --
-----------------------------------------------------------------------------------------------------------------------
local awful = require("awful")
local gears = require("gears")
local beautiful = require("beautiful")
local wibox = require("wibox")
local naughty = require("naughty")
local redflat = require("redflat")
local unpack = unpack or table.unpack
-- Initialize tables and vars for module
-----------------------------------------------------------------------------------------------------------------------
local env = {}
-- Build hotkeys depended on config parameters
-----------------------------------------------------------------------------------------------------------------------
function env:init(args)
-- init vars
args = args or {}
-- environment vars
self.terminal = args.terminal or "termite"
self.editor = os.getenv("EDITOR") or "vim"
self.editor_cmd = self.terminal .. " -e " .. self.editor
self.mod = args.mod or "Mod4"
self.fm = args.fm or "nemo"
self.mail = args.mail or "thunderbird"
self.player = args.player or self.terminal .. "-e ncmpcpp"
self.updates = args.updates or "bash -c 'pacman -Qu | grep -v ignored | wc -l'"
self.home = os.getenv("HOME")
self.themedir = awful.util.get_configuration_dir() .. "themes/default"
-- boolean defaults is pain
self.sloppy_focus = args.sloppy_focus or false
self.color_border_focus = args.color_border_focus or false
self.set_slave = args.set_slave == nil and true or false
self.set_center = args.set_center or false
self.desktop_autohide = args.desktop_autohide or false
-- theme setup
beautiful.init(env.themedir .. "/theme.lua")
beautiful.useless_gap = 5
-- naughty config
naughty.config.padding = beautiful.useless_gap and 2 * beautiful.useless_gap or 0
if beautiful.naughty then
naughty.config.presets.normal = redflat.util.table.merge(beautiful.naughty.base, beautiful.naughty.normal)
naughty.config.presets.critical = redflat.util.table.merge(beautiful.naughty.base, beautiful.naughty.critical)
naughty.config.presets.low = redflat.util.table.merge(beautiful.naughty.base, beautiful.naughty.low)
end
end
-- Common functions
-----------------------------------------------------------------------------------------------------------------------
-- Wallpaper setup
--------------------------------------------------------------------------------
env.wallpaper = function(s)
if beautiful.wallpaper then
if not env.desktop_autohide and awful.util.file_readable(beautiful.wallpaper) then
gears.wallpaper.maximized(beautiful.wallpaper, s, true)
else
gears.wallpaper.set(beautiful.color and beautiful.color.bg)
end
end
end
-- Tag tooltip text generation
--------------------------------------------------------------------------------
env.tagtip = function(t)
local layname = awful.layout.getname(awful.tag.getproperty(t, "layout"))
if redflat.util.table.check(beautiful, "widget.layoutbox.name_alias") then
layname = beautiful.widget.layoutbox.name_alias[layname] or layname
end
return string.format("%s (%d apps) [%s]", t.name, #(t:clients()), layname)
end
-- Panel widgets wrapper
--------------------------------------------------------------------------------
env.wrapper = function(widget, name, buttons)
local margin = redflat.util.table.check(beautiful, "widget.wrapper")
and beautiful.widget.wrapper[name] or { 0, 0, 0, 0 }
if buttons then
widget:buttons(buttons)
end
return wibox.container.margin(widget, unpack(margin))
end
-- End
-----------------------------------------------------------------------------------------------------------------------
return env

View File

@ -0,0 +1,27 @@
local naughty = require("naughty")
if awesome.startup_errors then
naughty.notify({
preset = naughty.config.presets.critical,
title = "Oops, there were errors during startup!",
text = awesome.startup_error
})
end
do
local in_error = false
awesome.connect_signal(
"debug::error",
function (err)
if in_error then return end
in_error = true
naughty.notify({
preset = naughty.config.presets.critical,
title = "Oops, an error happened!",
text = err
})
in_error = false
end
)
end

View File

@ -0,0 +1,871 @@
-----------------------------------------------------------------------------------------------------------------------
-- Hotkeys and mouse buttons config --
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
local table = table
local awful = require("awful")
local redflat = require("redflat")
-- Initialize tables and vars for module
-----------------------------------------------------------------------------------------------------------------------
local hotkeys = { mouse = {}, raw = {}, keys = {}, fake = {} }
-- key aliases
local apprunner = redflat.float.apprunner
local appswitcher = redflat.float.appswitcher
local current = redflat.widget.tasklist.filter.currenttags
local allscr = redflat.widget.tasklist.filter.allscreen
local laybox = redflat.widget.layoutbox
local redtip = redflat.float.hotkeys
local laycom = redflat.layout.common
local grid = redflat.layout.grid
local map = redflat.layout.map
local redtitle = redflat.titlebar
local qlaunch = redflat.float.qlaunch
-- Key support functions
-----------------------------------------------------------------------------------------------------------------------
-- change window focus by history
local function focus_to_previous()
awful.client.focus.history.previous()
if client.focus then client.focus:raise() end
end
-- change window focus by direction
local focus_switch_byd = function(dir)
return function()
awful.client.focus.bydirection(dir)
if client.focus then client.focus:raise() end
end
end
-- minimize and restore windows
local function minimize_all()
for _, c in ipairs(client.get()) do
if current(c, mouse.screen) then c.minimized = true end
end
end
local function minimize_all_except_focused()
for _, c in ipairs(client.get()) do
if current(c, mouse.screen) and c ~= client.focus then c.minimized = true end
end
end
local function restore_all()
for _, c in ipairs(client.get()) do
if current(c, mouse.screen) and c.minimized then c.minimized = false end
end
end
local function restore_client()
local c = awful.client.restore()
if c then client.focus = c; c:raise() end
end
-- close window
local function kill_all()
for _, c in ipairs(client.get()) do
if current(c, mouse.screen) and not c.sticky then c:kill() end
end
end
-- new clients placement
local function toggle_placement(env)
env.set_slave = not env.set_slave
redflat.float.notify:show({ text = (env.set_slave and "Slave" or "Master") .. " placement" })
end
-- numeric keys function builders
local function tag_numkey(i, mod, action)
return awful.key(
mod, "#" .. i + 9,
function ()
local screen = awful.screen.focused()
local tag = screen.tags[i]
if tag then action(tag) end
end
)
end
local function client_numkey(i, mod, action)
return awful.key(
mod, "#" .. i + 9,
function ()
if client.focus then
local tag = client.focus.screen.tags[i]
if tag then action(tag) end
end
end
)
end
-- brightness functions
local brightness = function(args)
redflat.float.brightness:change_with_xbacklight(args) -- use xbacklight
end
-- right bottom corner position
local rb_corner = function()
return { x = screen[mouse.screen].workarea.x + screen[mouse.screen].workarea.width,
y = screen[mouse.screen].workarea.y + screen[mouse.screen].workarea.height }
end
-- Build hotkeys depended on config parameters
-----------------------------------------------------------------------------------------------------------------------
function hotkeys:init(args)
-- Init vars
args = args or {}
local env = args.env
local volume = args.volume
local mainmenu = args.menu
local appkeys = args.appkeys or {}
self.mouse.root = (awful.util.table.join(
awful.button({ }, 3, function () mainmenu:toggle() end),
awful.button({ }, 4, awful.tag.viewnext),
awful.button({ }, 5, awful.tag.viewprev)
))
-- volume functions
local volume_raise = function() volume:change_volume({ show_notify = true }) end
local volume_lower = function() volume:change_volume({ show_notify = true, down = true }) end
local volume_mute = function() volume:mute() end
-- Init widgets
redflat.float.qlaunch:init()
-- Application hotkeys helper
--------------------------------------------------------------------------------
local apphelper = function(keys)
if not client.focus then return end
local app = client.focus.class:lower()
for name, sheet in pairs(keys) do
if name == app then
redtip:set_pack(
client.focus.class, sheet.pack, sheet.style.column, sheet.style.geometry,
function() redtip:remove_pack() end
)
redtip:show()
return
end
end
redflat.float.notify:show({ text = "No tips for " .. client.focus.class })
end
-- Keys for widgets
--------------------------------------------------------------------------------
-- Apprunner widget
------------------------------------------------------------
local apprunner_keys_move = {
{
{ env.mod }, "k", function() apprunner:down() end,
{ description = "Select next item", group = "Navigation" }
},
{
{ env.mod }, "i", function() apprunner:up() end,
{ description = "Select previous item", group = "Navigation" }
},
}
-- apprunner:set_keys(awful.util.table.join(apprunner.keys.move, apprunner_keys_move), "move")
apprunner:set_keys(apprunner_keys_move, "move")
-- Menu widget
------------------------------------------------------------
local menu_keys_move = {
{
{ env.mod }, "k", redflat.menu.action.down,
{ description = "Select next item", group = "Navigation" }
},
{
{ env.mod }, "i", redflat.menu.action.up,
{ description = "Select previous item", group = "Navigation" }
},
{
{ env.mod }, "j", redflat.menu.action.back,
{ description = "Go back", group = "Navigation" }
},
{
{ env.mod }, "l", redflat.menu.action.enter,
{ description = "Open submenu", group = "Navigation" }
},
}
-- redflat.menu:set_keys(awful.util.table.join(redflat.menu.keys.move, menu_keys_move), "move")
redflat.menu:set_keys(menu_keys_move, "move")
-- Appswitcher widget
------------------------------------------------------------
local appswitcher_keys = {
{
{ env.mod }, "a", function() appswitcher:switch() end,
{ description = "Select next app", group = "Navigation" }
},
{
{ env.mod, "Shift" }, "a", function() appswitcher:switch() end,
{} -- hidden key
},
{
{ env.mod }, "q", function() appswitcher:switch({ reverse = true }) end,
{ description = "Select previous app", group = "Navigation" }
},
{
{ env.mod, "Shift" }, "q", function() appswitcher:switch({ reverse = true }) end,
{} -- hidden key
},
{
{}, "Super_L", function() appswitcher:hide() end,
{ description = "Activate and exit", group = "Action" }
},
{
{ env.mod }, "Super_L", function() appswitcher:hide() end,
{} -- hidden key
},
{
{ env.mod, "Shift" }, "Super_L", function() appswitcher:hide() end,
{} -- hidden key
},
{
{}, "Return", function() appswitcher:hide() end,
{ description = "Activate and exit", group = "Action" }
},
{
{}, "Escape", function() appswitcher:hide(true) end,
{ description = "Exit", group = "Action" }
},
{
{ env.mod }, "Escape", function() appswitcher:hide(true) end,
{} -- hidden key
},
{
{ env.mod }, "F1", function() redtip:show() end,
{ description = "Show hotkeys helper", group = "Action" }
},
}
appswitcher:set_keys(appswitcher_keys)
-- Emacs like key sequences
--------------------------------------------------------------------------------
-- initial key
local keyseq = { { env.mod }, "c", {}, {} }
-- group
keyseq[3] = {
{ {}, "k", {}, {} }, -- application kill group
{ {}, "c", {}, {} }, -- client managment group
{ {}, "r", {}, {} }, -- client managment group
{ {}, "n", {}, {} }, -- client managment group
{ {}, "g", {}, {} }, -- run or rise group
{ {}, "f", {}, {} }, -- launch application group
}
-- quick launch key sequence actions
for i = 1, 9 do
local ik = tostring(i)
table.insert(keyseq[3][5][3], {
{}, ik, function() qlaunch:run_or_raise(ik) end,
{ description = "Run or rise application №" .. ik, group = "Run or Rise", keyset = { ik } }
})
table.insert(keyseq[3][6][3], {
{}, ik, function() qlaunch:run_or_raise(ik, true) end,
{ description = "Launch application №".. ik, group = "Quick Launch", keyset = { ik } }
})
end
-- application kill sequence actions
keyseq[3][1][3] = {
{
{}, "f", function() if client.focus then client.focus:kill() end end,
{ description = "Kill focused client", group = "Kill application", keyset = { "f" } }
},
{
{}, "a", kill_all,
{ description = "Kill all clients with current tag", group = "Kill application", keyset = { "a" } }
},
}
-- client managment sequence actions
keyseq[3][2][3] = {
{
{}, "p", function () toggle_placement(env) end,
{ description = "Switch master/slave window placement", group = "Clients managment", keyset = { "p" } }
},
}
keyseq[3][3][3] = {
{
{}, "f", restore_client,
{ description = "Restore minimized client", group = "Clients managment", keyset = { "f" } }
},
{
{}, "a", restore_all,
{ description = "Restore all clients with current tag", group = "Clients managment", keyset = { "a" } }
},
}
keyseq[3][4][3] = {
{
{}, "f", function() if client.focus then client.focus.minimized = true end end,
{ description = "Minimized focused client", group = "Clients managment", keyset = { "f" } }
},
{
{}, "a", minimize_all,
{ description = "Minimized all clients with current tag", group = "Clients managment", keyset = { "a" } }
},
{
{}, "e", minimize_all_except_focused,
{ description = "Minimized all clients except focused", group = "Clients managment", keyset = { "e" } }
},
}
-- Layouts
--------------------------------------------------------------------------------
-- shared layout keys
local layout_tile = {
{
{ env.mod }, "l", function () awful.tag.incmwfact( 0.05) end,
{ description = "Increase master width factor", group = "Layout" }
},
{
{ env.mod }, "j", function () awful.tag.incmwfact(-0.05) end,
{ description = "Decrease master width factor", group = "Layout" }
},
{
{ env.mod }, "i", function () awful.client.incwfact( 0.05) end,
{ description = "Increase window factor of a client", group = "Layout" }
},
{
{ env.mod }, "k", function () awful.client.incwfact(-0.05) end,
{ description = "Decrease window factor of a client", group = "Layout" }
},
{
{ env.mod, }, "+", function () awful.tag.incnmaster( 1, nil, true) end,
{ description = "Increase the number of master clients", group = "Layout" }
},
{
{ env.mod }, "-", function () awful.tag.incnmaster(-1, nil, true) end,
{ description = "Decrease the number of master clients", group = "Layout" }
},
{
{ env.mod, "Control" }, "+", function () awful.tag.incncol( 1, nil, true) end,
{ description = "Increase the number of columns", group = "Layout" }
},
{
{ env.mod, "Control" }, "-", function () awful.tag.incncol(-1, nil, true) end,
{ description = "Decrease the number of columns", group = "Layout" }
},
}
laycom:set_keys(layout_tile, "tile")
-- grid layout keys
local layout_grid_move = {
{
{ env.mod }, "KP_Up", function() grid.move_to("up") end,
{ description = "Move window up", group = "Movement" }
},
{
{ env.mod }, "KP_Down", function() grid.move_to("down") end,
{ description = "Move window down", group = "Movement" }
},
{
{ env.mod }, "KP_Left", function() grid.move_to("left") end,
{ description = "Move window left", group = "Movement" }
},
{
{ env.mod }, "KP_right", function() grid.move_to("right") end,
{ description = "Move window right", group = "Movement" }
},
{
{ env.mod, "Control" }, "KP_Up", function() grid.move_to("up", true) end,
{ description = "Move window up by bound", group = "Movement" }
},
{
{ env.mod, "Control" }, "KP_Down", function() grid.move_to("down", true) end,
{ description = "Move window down by bound", group = "Movement" }
},
{
{ env.mod, "Control" }, "KP_Left", function() grid.move_to("left", true) end,
{ description = "Move window left by bound", group = "Movement" }
},
{
{ env.mod, "Control" }, "KP_Right", function() grid.move_to("right", true) end,
{ description = "Move window right by bound", group = "Movement" }
},
}
local layout_grid_resize = {
{
{ env.mod }, "i", function() grid.resize_to("up") end,
{ description = "Inrease window size to the up", group = "Resize" }
},
{
{ env.mod }, "k", function() grid.resize_to("down") end,
{ description = "Inrease window size to the down", group = "Resize" }
},
{
{ env.mod }, "j", function() grid.resize_to("left") end,
{ description = "Inrease window size to the left", group = "Resize" }
},
{
{ env.mod }, "l", function() grid.resize_to("right") end,
{ description = "Inrease window size to the right", group = "Resize" }
},
{
{ env.mod, "Shift" }, "i", function() grid.resize_to("up", nil, true) end,
{ description = "Decrease window size from the up", group = "Resize" }
},
{
{ env.mod, "Shift" }, "k", function() grid.resize_to("down", nil, true) end,
{ description = "Decrease window size from the down", group = "Resize" }
},
{
{ env.mod, "Shift" }, "j", function() grid.resize_to("left", nil, true) end,
{ description = "Decrease window size from the left", group = "Resize" }
},
{
{ env.mod, "Shift" }, "l", function() grid.resize_to("right", nil, true) end,
{ description = "Decrease window size from the right", group = "Resize" }
},
{
{ env.mod, "Control" }, "i", function() grid.resize_to("up", true) end,
{ description = "Increase window size to the up by bound", group = "Resize" }
},
{
{ env.mod, "Control" }, "k", function() grid.resize_to("down", true) end,
{ description = "Increase window size to the down by bound", group = "Resize" }
},
{
{ env.mod, "Control" }, "j", function() grid.resize_to("left", true) end,
{ description = "Increase window size to the left by bound", group = "Resize" }
},
{
{ env.mod, "Control" }, "l", function() grid.resize_to("right", true) end,
{ description = "Increase window size to the right by bound", group = "Resize" }
},
{
{ env.mod, "Control", "Shift" }, "i", function() grid.resize_to("up", true, true) end,
{ description = "Decrease window size from the up by bound ", group = "Resize" }
},
{
{ env.mod, "Control", "Shift" }, "k", function() grid.resize_to("down", true, true) end,
{ description = "Decrease window size from the down by bound ", group = "Resize" }
},
{
{ env.mod, "Control", "Shift" }, "j", function() grid.resize_to("left", true, true) end,
{ description = "Decrease window size from the left by bound ", group = "Resize" }
},
{
{ env.mod, "Control", "Shift" }, "l", function() grid.resize_to("right", true, true) end,
{ description = "Decrease window size from the right by bound ", group = "Resize" }
},
}
redflat.layout.grid:set_keys(layout_grid_move, "move")
redflat.layout.grid:set_keys(layout_grid_resize, "resize")
-- user map layout keys
local layout_map_layout = {
{
{ env.mod }, "s", function() map.swap_group() end,
{ description = "Change placement direction for group", group = "Layout" }
},
{
{ env.mod }, "v", function() map.new_group(true) end,
{ description = "Create new vertical group", group = "Layout" }
},
{
{ env.mod }, "h", function() map.new_group(false) end,
{ description = "Create new horizontal group", group = "Layout" }
},
{
{ env.mod, "Control" }, "v", function() map.insert_group(true) end,
{ description = "Insert new vertical group before active", group = "Layout" }
},
{
{ env.mod, "Control" }, "h", function() map.insert_group(false) end,
{ description = "Insert new horizontal group before active", group = "Layout" }
},
{
{ env.mod }, "d", function() map.delete_group() end,
{ description = "Destroy group", group = "Layout" }
},
{
{ env.mod, "Control" }, "d", function() map.clean_groups() end,
{ description = "Destroy all empty groups", group = "Layout" }
},
{
{ env.mod }, "f", function() map.set_active() end,
{ description = "Set active group", group = "Layout" }
},
{
{ env.mod }, "g", function() map.move_to_active() end,
{ description = "Move focused client to active group", group = "Layout" }
},
{
{ env.mod, "Control" }, "f", function() map.hilight_active() end,
{ description = "Hilight active group", group = "Layout" }
},
{
{ env.mod }, "a", function() map.switch_active(1) end,
{ description = "Activate next group", group = "Layout" }
},
{
{ env.mod }, "q", function() map.switch_active(-1) end,
{ description = "Activate previous group", group = "Layout" }
},
{
{ env.mod }, "]", function() map.move_group(1) end,
{ description = "Move active group to the top", group = "Layout" }
},
{
{ env.mod }, "[", function() map.move_group(-1) end,
{ description = "Move active group to the bottom", group = "Layout" }
},
{
{ env.mod }, "r", function() map.reset_tree() end,
{ description = "Reset layout structure", group = "Layout" }
},
}
local layout_map_resize = {
{
{ env.mod }, "j", function() map.incfactor(nil, 0.1, false) end,
{ description = "Increase window horizontal size factor", group = "Resize" }
},
{
{ env.mod }, "l", function() map.incfactor(nil, -0.1, false) end,
{ description = "Decrease window horizontal size factor", group = "Resize" }
},
{
{ env.mod }, "i", function() map.incfactor(nil, 0.1, true) end,
{ description = "Increase window vertical size factor", group = "Resize" }
},
{
{ env.mod }, "k", function() map.incfactor(nil, -0.1, true) end,
{ description = "Decrease window vertical size factor", group = "Resize" }
},
{
{ env.mod, "Control" }, "j", function() map.incfactor(nil, 0.1, false, true) end,
{ description = "Increase group horizontal size factor", group = "Resize" }
},
{
{ env.mod, "Control" }, "l", function() map.incfactor(nil, -0.1, false, true) end,
{ description = "Decrease group horizontal size factor", group = "Resize" }
},
{
{ env.mod, "Control" }, "i", function() map.incfactor(nil, 0.1, true, true) end,
{ description = "Increase group vertical size factor", group = "Resize" }
},
{
{ env.mod, "Control" }, "k", function() map.incfactor(nil, -0.1, true, true) end,
{ description = "Decrease group vertical size factor", group = "Resize" }
},
}
redflat.layout.map:set_keys(layout_map_layout, "layout")
redflat.layout.map:set_keys(layout_map_resize, "resize")
-- Global keys
--------------------------------------------------------------------------------
self.raw.root = {
{
{ env.mod }, "F1", function() redtip:show() end,
{ description = "[Hold] Show awesome hotkeys helper", group = "Main" }
},
{
{ env.mod, "Control" }, "F1", function() apphelper(appkeys) end,
{ description = "[Hold] Show hotkeys helper for application", group = "Main" }
},
{
{ env.mod }, "c", function() redflat.float.keychain:activate(keyseq, "User") end,
{ description = "[Hold] User key sequence", group = "Main" }
},
{
{ env.mod }, "F2", function () redflat.service.navigator:run() end,
{ description = "[Hold] Tiling window control mode", group = "Window control" }
},
{
{ env.mod }, "h", function() redflat.float.control:show() end,
{ description = "[Hold] Floating window control mode", group = "Window control" }
},
{
{ env.mod }, "Return", function() awful.spawn(env.terminal) end,
{ description = "Open a terminal", group = "Actions" }
},
{
{ env.mod, "Mod1" }, "space", function() awful.spawn("clipflap --show") end,
{ description = "Clipboard manager", group = "Actions" }
},
{
{ env.mod, "Control" }, "r", awesome.restart,
{ description = "Reload WM", group = "Actions" }
},
{
{ env.mod }, "l", focus_switch_byd("right"),
{ description = "Go to right client", group = "Client focus" }
},
{
{ env.mod }, "j", focus_switch_byd("left"),
{ description = "Go to left client", group = "Client focus" }
},
{
{ env.mod }, "i", focus_switch_byd("up"),
{ description = "Go to upper client", group = "Client focus" }
},
{
{ env.mod }, "k", focus_switch_byd("down"),
{ description = "Go to lower client", group = "Client focus" }
},
{
{ env.mod }, "u", awful.client.urgent.jumpto,
{ description = "Go to urgent client", group = "Client focus" }
},
{
{ env.mod }, "Tab", focus_to_previous,
{ description = "Go to previos client", group = "Client focus" }
},
{
{ env.mod }, "w", function() mainmenu:show() end,
{ description = "Show main menu", group = "Widgets" }
},
{
{ env.mod }, "r", function() apprunner:show() end,
{ description = "Application launcher", group = "Widgets" }
},
{
{ env.mod }, "p", function() redflat.float.prompt:run() end,
{ description = "Show the prompt box", group = "Widgets" }
},
{
{ env.mod }, "x", function() redflat.float.top:show("cpu") end,
{ description = "Show the top process list", group = "Widgets" }
},
{
{ env.mod, "Control" }, "m", function() redflat.widget.mail:update(true) end,
{ description = "Check new mail", group = "Widgets" }
},
{
{ env.mod, "Control" }, "i", function() redflat.widget.minitray:toggle() end,
{ description = "Show minitray", group = "Widgets" }
},
{
{ env.mod, "Control" }, "u", function() redflat.widget.updates:update(true) end,
{ description = "Check available updates", group = "Widgets" }
},
{
{ env.mod }, "g", function() qlaunch:show() end,
{ description = "Application quick launcher", group = "Widgets" }
},
{
{ env.mod }, "y", function() laybox:toggle_menu(mouse.screen.selected_tag) end,
{ description = "Show layout menu", group = "Layouts" }
},
{
{ env.mod}, "Up", function() awful.layout.inc(1) end,
{ description = "Select next layout", group = "Layouts" }
},
{
{ env.mod }, "Down", function() awful.layout.inc(-1) end,
{ description = "Select previous layout", group = "Layouts" }
},
{
{}, "XF86MonBrightnessUp", function() brightness({ step = 2 }) end,
{ description = "Increase brightness", group = "Brightness control" }
},
{
{}, "XF86MonBrightnessDown", function() brightness({ step = 2, down = true }) end,
{ description = "Reduce brightness", group = "Brightness control" }
},
{
{}, "XF86AudioRaiseVolume", volume_raise,
{ description = "Increase volume", group = "Volume control" }
},
{
{}, "XF86AudioLowerVolume", volume_lower,
{ description = "Reduce volume", group = "Volume control" }
},
{
{}, "XF86AudioMute", volume_mute,
{ description = "Mute audio", group = "Volume control" }
},
{
{ env.mod }, "a", nil, function() appswitcher:show({ filter = current }) end,
{ description = "Switch to next with current tag", group = "Application switcher" }
},
{
{ env.mod }, "q", nil, function() appswitcher:show({ filter = current, reverse = true }) end,
{ description = "Switch to previous with current tag", group = "Application switcher" }
},
{
{ env.mod, "Shift" }, "a", nil, function() appswitcher:show({ filter = allscr }) end,
{ description = "Switch to next through all tags", group = "Application switcher" }
},
{
{ env.mod, "Shift" }, "q", nil, function() appswitcher:show({ filter = allscr, reverse = true }) end,
{ description = "Switch to previous through all tags", group = "Application switcher" }
},
{
{ env.mod }, "Escape", awful.tag.history.restore,
{ description = "Go previos tag", group = "Tag navigation" }
},
{
{ env.mod }, "Right", awful.tag.viewnext,
{ description = "View next tag", group = "Tag navigation" }
},
{
{ env.mod }, "Left", awful.tag.viewprev,
{ description = "View previous tag", group = "Tag navigation" }
},
{
{ env.mod }, "t", function() redtitle.toggle(client.focus) end,
{ description = "Show/hide titlebar for focused client", group = "Titlebar" }
},
--{
-- { env.mod, "Control" }, "t", function() redtitle.switch(client.focus) end,
-- { description = "Switch titlebar view for focused client", group = "Titlebar" }
--},
{
{ env.mod, "Shift" }, "t", function() redtitle.toggle_all() end,
{ description = "Show/hide titlebar for all clients", group = "Titlebar" }
},
{
{ env.mod, "Control", "Shift" }, "t", function() redtitle.global_switch() end,
{ description = "Switch titlebar view for all clients", group = "Titlebar" }
},
{
{ env.mod }, "e", function() redflat.float.player:show(rb_corner()) end,
{ description = "Show/hide widget", group = "Audio player" }
},
{
{}, "XF86AudioPlay", function() redflat.float.player:action("PlayPause") end,
{ description = "Play/Pause track", group = "Audio player" }
},
{
{}, "XF86AudioNext", function() redflat.float.player:action("Next") end,
{ description = "Next track", group = "Audio player" }
},
{
{}, "XF86AudioPrev", function() redflat.float.player:action("Previous") end,
{ description = "Previous track", group = "Audio player" }
},
{
{ env.mod, "Control" }, "s", function() for s in screen do env.wallpaper(s) end end,
{} -- hidden key
}
}
-- Client keys
--------------------------------------------------------------------------------
self.raw.client = {
{
{ env.mod }, "f", function(c) c.fullscreen = not c.fullscreen; c:raise() end,
{ description = "Toggle fullscreen", group = "Client keys" }
},
{
{ env.mod }, "F4", function(c) c:kill() end,
{ description = "Close", group = "Client keys" }
},
{
{ env.mod, "Control" }, "f", awful.client.floating.toggle,
{ description = "Toggle floating", group = "Client keys" }
},
{
{ env.mod, "Control" }, "o", function(c) c.ontop = not c.ontop end,
{ description = "Toggle keep on top", group = "Client keys" }
},
{
{ env.mod }, "n", function(c) c.minimized = true end,
{ description = "Minimize", group = "Client keys" }
},
{
{ env.mod }, "m", function(c) c.maximized = not c.maximized; c:raise() end,
{ description = "Maximize", group = "Client keys" }
}
}
self.keys.root = redflat.util.key.build(self.raw.root)
self.keys.client = redflat.util.key.build(self.raw.client)
-- Numkeys
--------------------------------------------------------------------------------
-- add real keys without description here
for i = 1, 9 do
self.keys.root = awful.util.table.join(
self.keys.root,
tag_numkey(i, { env.mod }, function(t) t:view_only() end),
tag_numkey(i, { env.mod, "Control" }, function(t) awful.tag.viewtoggle(t) end),
client_numkey(i, { env.mod, "Shift" }, function(t) client.focus:move_to_tag(t) end),
client_numkey(i, { env.mod, "Control", "Shift" }, function(t) client.focus:toggle_tag(t) end)
)
end
-- make fake keys with description special for key helper widget
local numkeys = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }
self.fake.numkeys = {
{
{ env.mod }, "1..9", nil,
{ description = "Switch to tag", group = "Numeric keys", keyset = numkeys }
},
{
{ env.mod, "Control" }, "1..9", nil,
{ description = "Toggle tag", group = "Numeric keys", keyset = numkeys }
},
{
{ env.mod, "Shift" }, "1..9", nil,
{ description = "Move focused client to tag", group = "Numeric keys", keyset = numkeys }
},
{
{ env.mod, "Control", "Shift" }, "1..9", nil,
{ description = "Toggle focused client on tag", group = "Numeric keys", keyset = numkeys }
},
}
-- Hotkeys helper setup
--------------------------------------------------------------------------------
redflat.float.hotkeys:set_pack("Main", awful.util.table.join(self.raw.root, self.raw.client, self.fake.numkeys), 2)
-- Mouse buttons
--------------------------------------------------------------------------------
self.mouse.client = awful.util.table.join(
awful.button({}, 1, function (c) client.focus = c; c:raise() end),
awful.button({}, 2, awful.mouse.client.move),
awful.button({ env.mod }, 3, awful.mouse.client.resize),
awful.button({}, 8, function(c) c:kill() end)
)
-- Set root hotkeys
--------------------------------------------------------------------------------
root.keys(self.keys.root)
root.buttons(self.mouse.root)
end
-- End
-----------------------------------------------------------------------------------------------------------------------
return hotkeys

View File

@ -0,0 +1,86 @@
-----------------------------------------------------------------------------------------------------------------------
-- Layouts config --
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
local awful = require("awful")
local redflat = require("redflat")
-- Initialize tables and vars for module
-----------------------------------------------------------------------------------------------------------------------
local layouts = {}
-- Build table
-----------------------------------------------------------------------------------------------------------------------
function layouts:init()
-- layouts list
local layset = {
awful.layout.suit.floating,
redflat.layout.grid,
awful.layout.suit.tile,
awful.layout.suit.tile.left,
awful.layout.suit.tile.bottom,
awful.layout.suit.tile.top,
awful.layout.suit.fair,
redflat.layout.map,
awful.layout.suit.max,
awful.layout.suit.max.fullscreen,
}
awful.layout.layouts = layset
end
-- some advanced layout settings
redflat.layout.map.notification = false
-- connect alternative moving handler to allow using custom handler per layout
-- by now custom handler provided for 'redflat.layout.grid' only
-- feel free to remove if you don't use this one
client.disconnect_signal("request::geometry", awful.layout.move_handler)
client.connect_signal("request::geometry", redflat.layout.common.mouse.move)
-- connect additional signal for 'redflat.layout.map'
-- this one removing client in smart way and correct tiling scheme
-- feel free to remove if you want to restore plain queue behavior
client.connect_signal("unmanage", redflat.layout.map.clean_client)
client.connect_signal("property::minimized", function(c)
if c.minimized and redflat.layout.map.check_client(c) then redflat.layout.map.clean_client(c) end
end)
client.connect_signal("property::floating", function(c)
if c.floating and redflat.layout.map.check_client(c) then redflat.layout.map.clean_client(c) end
end)
client.connect_signal("untagged", function(c, t)
if redflat.layout.map.data[t] then redflat.layout.map.clean_client(c) end
end)
-- user map layout preset
-- preset can be defined for individual tags, but this should be done after tag initialization
-- redflat.layout.map.base_construct = function(wa)
-- local tree = { set = {}, active = 1, autoaim = true }
-- tree.set[1] = redflat.layout.map.construct_itempack({}, wa, false)
-- tree.set[2] = redflat.layout.map.base_set_new_pack({}, wa, true, tree.set[1])
-- tree.set[3] = redflat.layout.map.base_set_new_pack({}, wa, true, tree.set[1])
-- tree.set[4] = redflat.layout.map.base_set_new_pack({}, wa, true, tree.set[1])
-- function tree:aim()
-- for i = 2, 4 do if #self.set[i].items == 0 then return i end end
-- local active = #self.set[4].items >= #self.set[2].items and 2 or 4
-- return active
-- end
-- return tree
-- end
-- End
-----------------------------------------------------------------------------------------------------------------------
return layouts

View File

@ -0,0 +1,96 @@
local beautiful = require("beautiful")
local redflat = require("redflat")
local awful = require("awful")
local naughty = require("naughty")
-- Initialize tables and vars for module
-----------------------------------------------------------------------------------------------------------------------
local menu = {}
-- Build function
--------------------------------------------------------------------------------
function menu:init(args)
-- vars
args = args or {}
local env = args.env or {} -- fix this?
local separator = args.separator or { widget = redflat.gauge.separator.horizontal() }
local theme = args.theme or { auto_hotkey = true }
local icon_style = args.icon_style or { custom_only = true, scalable_only = true }
-- theme vars
local default_icon = redflat.util.base.placeholder()
local icon = redflat.util.table.check(beautiful, "icon.awesome") and beautiful.icon.awesome or default_icon
local color = redflat.util.table.check(beautiful, "color.icon") and beautiful.color.icon or nil
-- icon finder
local function micon(name)
return redflat.service.dfparser.lookup_icon(name, icon_style)
end
-- extra commands
local ranger_comm = env.terminal .. " -e ranger"
-- Application submenu
------------------------------------------------------------
local appmenu = redflat.service.dfparser.menu({ icons = icon_style, wm_name = "awesome" })
-- Awesome submenu
------------------------------------------------------------
local awesomemenu = {
{ "Restart", awesome.restart, micon("gnome-session-reboot") },
separator,
{ "Awesome config", env.fm .. " ~/.config/awesome", micon("folder-bookmarks") },
}
-- Places submenu
------------------------------------------------------------
local placesmenu = {
{ "Documents", env.fm .. " Documents", micon("folder-documents") },
{ "Downloads", env.fm .. " Downloads", micon("folder-download") },
{ "Music", env.fm .. " Music", micon("folder-music") },
{ "Pictures", env.fm .. " Pictures", micon("folder-pictures") },
{ "Videos", env.fm .. " Videos", micon("folder-videos") },
separator,
{ "Media", env.fm .. " /mnt/media", micon("folder-bookmarks") },
{ "Storage", env.fm .. " /mnt/storage", micon("folder-bookmarks") },
}
-- Exit submenu
------------------------------------------------------------
local exitmenu = {
{ "Reboot", "reboot", micon("gnome-session-reboot") },
{ "Shutdown", "shutdown now", micon("system-shutdown") },
separator,
{ "Switch user", "dm-tool switch-to-greeter", micon("gnome-session-switch") },
{ "Suspend", "systemctl suspend" , micon("gnome-session-suspend") },
{ "Log out", awesome.quit, micon("exit") },
}
-- Main menu
------------------------------------------------------------
self.mainmenu = redflat.menu({ theme = theme,
items = {
{ "Awesome", awesomemenu, micon("awesome") },
{ "Applications", appmenu, micon("distributor-logo") },
{ "Places", placesmenu, micon("folder_home"), key = "c" },
separator,
{ "Terminal", env.terminal, micon("terminal") },
{ "Nemo", env.fm, micon("folder"), key = "n" },
{ "Ranger", ranger_comm, micon("folder"), key = "r" },
{ "Editor", "emacs", micon("emacs") },
separator,
{ "Exit", exitmenu, micon("exit") },
}
})
-- Menu panel widget
------------------------------------------------------------
self.widget = redflat.gauge.svgbox(icon, nil, color)
self.buttons = awful.util.table.join(
awful.button({ }, 1, function () self.mainmenu:toggle() end)
)
end
return menu

View File

@ -0,0 +1,2 @@
# IDE settings
.idea

View File

@ -0,0 +1,39 @@
-- Only allow symbols available in all Lua versions
std = "min"
-- Get rid of "unused argument self" - warnings
self = false
-- Do not check files outside of config
exclude_files = {
".luacheckrc", -- this file itself
}
-- Global objects defined by the C code
read_globals = {
-- awesome API
"awesome",
"button",
"dbus",
"drawable",
"drawin",
"key",
"keygrabber",
"mousegrabber",
"root",
"selection",
"tag",
"window",
-- lua unpack
"table.unpack",
"unpack"
}
-- Not read-only globals.
globals = {
-- awesome API
"screen",
"mouse",
"client",
}

View File

@ -0,0 +1,201 @@
-----------------------------------------------------------------------------------------------------------------------
-- Red Flat calendar desktop widget --
-----------------------------------------------------------------------------------------------------------------------
-- Multi monitoring widget
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local os = os
local string = string
local setmetatable = setmetatable
local wibox = require("wibox")
local beautiful = require("beautiful")
local color = require("gears.color")
local timer = require("gears.timer")
local redutil = require("redflat.util")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local calendar = { mt = {} }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
show_pointer = true,
label = { gap = 12, font = { font = "Sans", size = 18, face = 1, slant = 0 }, sep = "-" },
mark = { height = 20, width = 40, dx = 10, line = 4 },
color = { main = "#b1222b", wibox = "#161616", gray = "#404040", bg = "#161616" }
}
return redutil.table.merge(style, redutil.table.check(beautiful, "desktop.calendar") or {})
end
local days_in_month = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
-- Support functions
-----------------------------------------------------------------------------------------------------------------------
local function is_leap_year(year)
return year % 4 == 0 and (year % 100 ~= 0 or year % 400 == 0)
end
-- Drawing function
-----------------------------------------------------------------------------------------------------------------------
local function daymarks(style)
-- Create custom widget
--------------------------------------------------------------------------------
local widg = wibox.widget.base.make_widget()
widg._data = {
gap = 1,
label_x = 0,
pointer = { show = false, index = 1, label = "01-01" },
days = 31,
marks = 31,
today = 1,
label = "01-01",
weekend = { 6, 0 }
}
-- User functions
------------------------------------------------------------
function widg:update_data()
local date = os.date('*t')
local first_week_day = os.date('%w', os.time({ year = date.year, month = date.month, day = 1 }))
self._data.today = date.day
self._data.days = date.month == 2 and is_leap_year(date.year) and 29 or days_in_month[date.month]
self._data.weekend = { (7 - first_week_day) % 7, (8 - first_week_day) % 7 }
self._data.label = string.format("%.2d%s%.2d", date.day, style.label.sep, date.month)
self:emit_signal("widget::redraw_needed")
end
function widg:update_pointer(show, index)
self._data.pointer.show = show
if index then self._data.pointer.index = index end
local date = os.date('*t')
self._data.pointer.label = string.format("%.2d%s%.2d", self._data.pointer.index, style.label.sep, date.month)
self:emit_signal("widget::redraw_needed")
end
-- Fit
------------------------------------------------------------
function widg:fit(_, width, height)
return width, height
end
-- Draw
------------------------------------------------------------
function widg:draw(_, cr, width, height)
-- main draw
self._data.gap = (height - self._data.days * style.mark.height) / (self._data.days - 1)
self._data.label_x = width - style.mark.width - style.mark.dx - style.label.gap
cr:set_line_width(style.mark.line)
for i = 1, self._data.days do
-- calendar marks
local id = i % 7
local is_weekend = id == self._data.weekend[1] or id == self._data.weekend[2]
cr:set_source(color(is_weekend and style.color.main or style.color.gray))
cr:move_to(width, (style.mark.height + self._data.gap) * (i - 1))
cr:rel_line_to(0, style.mark.height)
cr:rel_line_to(-style.mark.width, 0)
cr:rel_line_to(-style.mark.dx, -style.mark.height / 2)
cr:rel_line_to(style.mark.dx, -style.mark.height / 2)
cr:close_path()
cr:fill()
-- data label
if i == self._data.today or (self._data.pointer.show and i == self._data.pointer.index) then
cr:set_source(color(i == self._data.today and style.color.main or style.color.gray))
local coord_y = ((style.mark.height + self._data.gap) * (i - 1)) + style.mark.height / 2
redutil.cairo.set_font(cr, style.label.font)
local ext = cr:text_extents(self._data.label)
cr:move_to(
self._data.label_x - (ext.width + 2 * ext.x_bearing), coord_y - (ext.height/2 + ext.y_bearing)
)
cr:show_text(i == self._data.today and self._data.label or self._data.pointer.label)
end
end
end
--------------------------------------------------------------------------------
return widg
end
-- Create a new widget
-----------------------------------------------------------------------------------------------------------------------
function calendar.new(args, style)
-- Initialize vars
--------------------------------------------------------------------------------
local dwidget = {}
args = args or {}
style = redutil.table.merge(default_style(), style or {})
local timeout = args.timeout or 300
dwidget.style = style
-- Create calendar widget
--------------------------------------------------------------------------------
dwidget.calendar = daymarks(style)
dwidget.area = wibox.container.margin(dwidget.calendar)
-- Set update timer
--------------------------------------------------------------------------------
local t = timer({ timeout = timeout })
t:connect_signal("timeout", function () dwidget.calendar:update_data() end)
t:start()
t:emit_signal("timeout")
-- Drawing date label under mouse
--------------------------------------------------------------------------------
function dwidget:activate_wibox(wbox)
if style.show_pointer then
wbox:connect_signal("mouse::move", function(_, x, y)
local show_poiter = false
local index
if x > self.calendar._data.label_x then
for i = 1, self.calendar._data.days do
local cy = y - (i - 1) * (self.calendar._data.gap + style.mark.height)
if cy > 0 and cy < style.mark.height then
show_poiter = true
index = i
break
end
end
end
if self.calendar._data.pointer.show ~= show_poiter then
self.calendar:update_pointer(show_poiter, index)
end
end)
wbox:connect_signal("mouse::leave", function()
if self.calendar._data.pointer.show then self.calendar:update_pointer(false) end
end)
end
end
--------------------------------------------------------------------------------
return dwidget
end
-- Config metatable to call module as function
-----------------------------------------------------------------------------------------------------------------------
function calendar.mt:__call(...)
return calendar.new(...)
end
return setmetatable(calendar, calendar.mt)

View File

@ -0,0 +1,10 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat library --
-----------------------------------------------------------------------------------------------------------------------
local wrequire = require("redflat.util").wrequire
local setmetatable = setmetatable
local lib = { _NAME = "redflat.desktop.common.bar" }
return setmetatable(lib, { __index = wrequire })

View File

@ -0,0 +1,114 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat desktop progressbar widget --
-----------------------------------------------------------------------------------------------------------------------
-- Dashed horizontal progress bar
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local math = math
local wibox = require("wibox")
local color = require("gears.color")
local beautiful = require("beautiful")
local redutil = require("redflat.util")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local progressbar = { mt = {} }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
maxm = 1,
width = nil,
height = nil,
chunk = { gap = 5, width = 5 },
autoscale = false,
color = { main = "#b1222b", gray = "#404040" }
}
return redutil.table.merge(style, redutil.table.check(beautiful, "desktop.common.bar.plain") or {})
end
-- Cairo drawing functions
-----------------------------------------------------------------------------------------------------------------------
local function draw_progressbar(cr, width, height, gap, first_point, last_point, fill_color)
cr:set_source(color(fill_color))
for i = first_point, last_point do
cr:rectangle((i - 1) * (width + gap), 0, width, height)
end
cr:fill()
end
-- Create a new progressbar widget
-- @param style.chunk Table containing dash parameters
-- @param style.color.main Main color
-- @param style.width Widget width (optional)
-- @param style.height Widget height (optional)
-- @param style.autoscale Scaling received values, true by default
-- @param style.maxm Scaling value if autoscale = false
-----------------------------------------------------------------------------------------------------------------------
function progressbar.new(style)
-- Initialize vars
--------------------------------------------------------------------------------
style = redutil.table.merge(default_style(), style or {})
local maxm = style.maxm
--style aliases
local stg, stw = style.chunk.gap, style.chunk.width
-- Create custom widget
--------------------------------------------------------------------------------
local widg = wibox.widget.base.make_widget()
widg._data = { value = 0, chunks = 1, gap = 1, cnum = 0 }
function widg:set_value(x)
if style.autoscale then
if x > maxm then maxm = x end
end
local cx = x / maxm
if cx > 1 then cx = 1 end
self._data.value = cx
local num = math.ceil(self._data.chunks * self._data.value)
if num ~= self._data.cnum then
self:emit_signal("widget::redraw_needed")
end
end
function widg:fit(_, width, height)
local w = style.width and math.min(style.width, width) or width
local h = style.height and math.min(style.height, height) or height
return w, h
end
-- Draw function
------------------------------------------------------------
function widg:draw(_, cr, width, height)
-- progressbar
self._data.chunks = math.floor((width + stg) / (stw + stg))
self._data.gap = stg + (width - (self._data.chunks - 1) * (stw + stg) - stw) / (self._data.chunks - 1)
self._data.cnum = math.ceil(self._data.chunks * self._data.value)
draw_progressbar(cr, stw, height, self._data.gap, 1, self._data.cnum, style.color.main)
draw_progressbar(cr, stw, height, self._data.gap, self._data.cnum + 1, self._data.chunks, style.color.gray)
end
--------------------------------------------------------------------------------
return widg
end
-- Config metatable to call progressbar module as function
-----------------------------------------------------------------------------------------------------------------------
function progressbar.mt:__call(...)
return progressbar.new(...)
end
return setmetatable(progressbar, progressbar.mt)

View File

@ -0,0 +1,144 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat corners widget --
-----------------------------------------------------------------------------------------------------------------------
-- Vertical progress indicator with custom shape
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local math = math
local wibox = require("wibox")
local color = require("gears.color")
local beautiful = require("beautiful")
local redutil = require("redflat.util")
local tooltip = require("redflat.float.tooltip")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local progressbar = { mt = {} }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
chunk = { num = 10, line = 5, height = 10 },
maxm = 1,
width = nil,
height = nil,
autoscale = false,
show = { tooltip = false },
tooltip = {},
shape = "corner",
color = { main = "#b1222b", gray = "#404040" }
}
return redutil.table.merge(style, redutil.table.check(beautiful, "desktop.common.bar.shaped") or {})
end
-- Cairo drawing functions
-----------------------------------------------------------------------------------------------------------------------
local function draw_corner(cr, width, height, gap, first_point, last_point, fill_color, style)
cr:set_source(color(fill_color))
for i = first_point, last_point do
cr:move_to(0, height - (i - 1) * (style.chunk.line + gap))
cr:rel_line_to(width / 2, - style.chunk.height)
cr:rel_line_to(width / 2, style.chunk.height)
cr:rel_line_to(- style.chunk.line, 0)
cr:rel_line_to(- width / 2 + style.chunk.line, - style.chunk.height + style.chunk.line)
cr:rel_line_to(- width / 2 + style.chunk.line, style.chunk.height - style.chunk.line)
cr:close_path()
end
cr:fill()
end
local function draw_line(cr, width, height, dy, first_point, last_point, fill_color, style)
cr:set_source(color(fill_color))
for i = first_point, last_point do
cr:rectangle(0, height - (i - 1) * dy, width, - style.chunk.line)
end
cr:fill()
end
-- Create a new progressbar widget
-- @param style.chunk Table containing number and sizes for progress bar chunks
-- @param style.color Main color
-- @param style.width Widget width (optional)
-- @param style.height Widget height (optional)
-- @param style.autoscale Scaling received values, true by default
-- @param style.maxm Scaling value if autoscale = false
-----------------------------------------------------------------------------------------------------------------------
function progressbar.new(style)
-- Initialize vars
--------------------------------------------------------------------------------
style = redutil.table.merge(default_style(), style or {})
local maxm = style.maxm
--style aliases
local stn = style.chunk.num
-- Create custom widget
--------------------------------------------------------------------------------
local shapewidg = wibox.widget.base.make_widget()
shapewidg._data = { value = 0, cnum = 0 }
-- tooltip
if style.show.tooltip then
shapewidg._tp = tooltip({ objects = { shapewidg } }, style.tooltip)
end
-- setup
function shapewidg:set_value(x)
if style.autoscale then
if x > maxm then maxm = x end
end
local cx = x / maxm
if cx > 1 then cx = 1 end
self._data.value = cx
local num = math.ceil(stn * self._data.value)
if num ~= self._data.cnum then
self:emit_signal("widget::redraw_needed")
end
end
function shapewidg:set_tip(txt)
if self._tp then self._tp:set_text(txt) end
end
function shapewidg:fit(_, width, height)
return style.width or width, style.height or height
end
-- Draw function
------------------------------------------------------------
function shapewidg:draw(_, cr, width, height)
self._data.cnum = math.ceil(stn * self._data.value)
if style.shape == "plain" then
local line_gap = style.chunk.line + (height - style.chunk.line * stn)/(stn - 1)
draw_line(cr, width, height, line_gap, 1, self._data.cnum, style.color.main, style)
draw_line(cr, width, height, line_gap, self._data.cnum + 1, stn, style.color.gray, style)
elseif style.shape == "corner" then
local corner_gap = (height - (stn - 1) * style.chunk.line - style.chunk.height) / (stn - 1)
draw_corner(cr, width, height, corner_gap, 1, self._data.cnum, style.color.main, style)
draw_corner(cr, width, height, corner_gap, self._data.cnum + 1, stn, style.color.gray, style)
end
end
--------------------------------------------------------------------------------
return shapewidg
end
-- Config metatable to call progressbar module as function
-----------------------------------------------------------------------------------------------------------------------
function progressbar.mt:__call(...)
return progressbar.new(...)
end
return setmetatable(progressbar, progressbar.mt)

View File

@ -0,0 +1,121 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat chart widget --
-----------------------------------------------------------------------------------------------------------------------
-- History graph for desktop widgets
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local table = table
local ipairs = ipairs
local math = math
local wibox = require("wibox")
local color = require("gears.color")
local beautiful = require("beautiful")
local redutil = require("redflat.util")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local chart = { mt = {} }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
maxm = 1,
color = "#404040",
width = nil,
height = nil,
zero_height = 4,
bar = { gap = 5, width = 5 },
autoscale = true
}
return redutil.table.merge(style, redutil.table.check(beautiful, "desktop.common.chart") or {})
end
-- Create a new chart widget
-- @param style Table containing chart geometry and style
-- See block of local vars below for more details
-----------------------------------------------------------------------------------------------------------------------
function chart.new(style)
-- Initialize vars
--------------------------------------------------------------------------------
style = redutil.table.merge(default_style(), style or {})
local count = 0
local barnum
local current_maxm = style.maxm
-- updating values
local data = {
values = {}
}
-- Create custom widget
--------------------------------------------------------------------------------
local chartwidg = wibox.widget.base.make_widget()
-- User functions
------------------------------------------------------------
function chartwidg:set_value(x)
if barnum then
count = count % barnum + 1
data.values[count] = x
self:emit_signal("widget::redraw_needed")
end
end
-- Fit function
------------------------------------------------------------
function chartwidg:fit(_, width, height)
return style.width or width, style.height or height
end
-- Draw function
------------------------------------------------------------
function chartwidg:draw(_, cr, width, height)
--scale
if style.autoscale then
current_maxm = style.maxm
for _, v in ipairs(data.values) do
if v > current_maxm then current_maxm = v end
end
end
-- chart
barnum = math.floor((width + style.bar.gap) / (style.bar.width + style.bar.gap))
while #data.values < barnum do
table.insert(data.values, 0)
end
local real_gap = style.bar.gap + (width - (barnum - 1) * (style.bar.width + style.bar.gap)
- style.bar.width) / (barnum - 1)
cr:set_source(color(style.color))
for i = 0, barnum - 1 do
local n = (count + i) % barnum + 1
local k = data.values[n] / current_maxm
if k > 1 then k = 1 end
local bar_height = - k * (height - style.zero_height)
cr:rectangle(i * (style.bar.width + real_gap), height, style.bar.width, - style.zero_height)
cr:rectangle(i * (style.bar.width + real_gap), height - style.zero_height, style.bar.width, bar_height)
end
cr:fill()
end
--------------------------------------------------------------------------------
return chartwidg
end
-- Config metatable to call chart module as function
-----------------------------------------------------------------------------------------------------------------------
function chart.mt:__call(...)
return chart.new(...)
end
return setmetatable(chart, chart.mt)

View File

@ -0,0 +1,10 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat library --
-----------------------------------------------------------------------------------------------------------------------
local wrequire = require("redflat.util").wrequire
local setmetatable = setmetatable
local lib = { _NAME = "redflat.desktop.common" }
return setmetatable(lib, { __index = wrequire })

View File

@ -0,0 +1,10 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat library --
-----------------------------------------------------------------------------------------------------------------------
local wrequire = require("redflat.util").wrequire
local setmetatable = setmetatable
local lib = { _NAME = "redflat.desktop.common.pack" }
return setmetatable(lib, { __index = wrequire })

View File

@ -0,0 +1,137 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat barpack widget --
-----------------------------------------------------------------------------------------------------------------------
-- Group of indicators with progressbar, label and text in every line
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local wibox = require("wibox")
local beautiful = require("beautiful")
local redutil = require("redflat.util")
local dcommon = require("redflat.desktop.common")
local tooltip = require("redflat.float.tooltip")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local barpack = { mt = {} }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
label = {},
text = {},
show = { text = true, label = true, tooltip = false },
progressbar = {},
line = { height = 20 },
gap = { text = 20, label = 20 },
tooltip = {},
color = {}
}
return redutil.table.merge(style, redutil.table.check(beautiful, "desktop.common.pack.lines") or {})
end
-- Create a new barpack widget
-----------------------------------------------------------------------------------------------------------------------
function barpack.new(num, style)
local pack = {}
style = redutil.table.merge(default_style(), style or {})
local progressbar_style = redutil.table.merge(style.progressbar, { color = style.color })
local label_style = redutil.table.merge(style.label, { color = style.color.gray })
local text_style = redutil.table.merge(style.text, { color = style.color.gray })
-- Construct group of lines
--------------------------------------------------------------------------------
pack.layout = wibox.layout.align.vertical()
local flex_vertical = wibox.layout.flex.vertical()
local lines = {}
for i = 1, num do
lines[i] = {}
-- bar
local line_align = wibox.layout.align.horizontal()
line_align:set_forced_height(style.line.height)
lines[i].bar = dcommon.bar.plain(progressbar_style)
line_align:set_middle(lines[i].bar)
-- label
lines[i]._label_txt = ""
lines[i].label = dcommon.textbox("", label_style)
lines[i].label:set_width(0)
lines[i].label_margin = wibox.container.margin(lines[i].label)
line_align:set_left(lines[i].label_margin)
-- value text
lines[i].text = dcommon.textbox("", text_style)
lines[i].text:set_width(0)
lines[i].text_margin = wibox.container.margin(lines[i].text)
line_align:set_right(lines[i].text_margin)
-- tooltip
if style.show.tooltip then
lines[i].tooltip = tooltip({ objects = { line_align } }, style.tooltip)
end
if i == 1 then
pack.layout:set_top(line_align)
else
local line_space = wibox.layout.align.vertical()
line_space:set_bottom(line_align)
flex_vertical:add(line_space)
end
end
pack.layout:set_middle(flex_vertical)
-- Setup functions
--------------------------------------------------------------------------------
function pack:set_values(value, index)
lines[index].bar:set_value(value)
end
function pack:set_text(value, index)
if style.show.text then
lines[index].text:set_text(value)
lines[index].text:set_width(value and text_style.width or 0)
lines[index].text_margin:set_left(value and style.gap.text or 0)
end
if lines[index].tooltip then
lines[index].tooltip:set_text(string.format("%s %s", lines[index]._label_txt, value))
end
end
function pack:set_text_color(value, index)
lines[index].text:set_color(value)
end
function pack:set_label_color(value, index)
lines[index].label:set_color(value)
end
function pack:set_label(value, index)
lines[index]._label_txt = value
if style.show.label then
lines[index].label:set_text(value)
lines[index].label:set_width(value and label_style.width or 0)
lines[index].label_margin:set_right(value and style.gap.label or 0)
end
end
--------------------------------------------------------------------------------
return pack
end
-- Config metatable to call barpack module as function
-----------------------------------------------------------------------------------------------------------------------
function barpack.mt:__call(...)
return barpack.new(...)
end
return setmetatable(barpack, barpack.mt)

View File

@ -0,0 +1,72 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat ber pack widget --
-----------------------------------------------------------------------------------------------------------------------
-- Group of upright indicators placed in horizontal layout
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local wibox = require("wibox")
--local beautiful = require("beautiful")
local progressbar = require("redflat.desktop.common.bar.shaped")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local barpack = { mt = {} }
-- Create a new barpack widget
-- @param num Number of indicators
-- @param style Style variables for redflat shaped progressbar widget
-----------------------------------------------------------------------------------------------------------------------
function barpack.new(num, style)
local pack = {}
style = style or {}
-- construct group of bar indicators
pack.layout = wibox.layout.align.horizontal()
local flex_horizontal = wibox.layout.flex.horizontal()
local crn = {}
for i = 1, num do
crn[i] = progressbar(style)
if i == 1 then
pack.layout:set_left(crn[i])
else
local bar_space = wibox.layout.align.horizontal()
bar_space:set_right(crn[i])
flex_horizontal:add(bar_space)
end
end
pack.layout:set_middle(flex_horizontal)
-- setup functions
function pack:set_values(values, n, tip)
if n then
if crn[n] then
crn[n]:set_value(values)
if tip then crn[n]:set_tip(tip) end
end
else
for i, v in ipairs(values) do
if crn[i] then
crn[i]:set_value(v)
if tip then crn[n]:set_tip(tip) end
end
end
end
end
return pack
end
-- Config metatable to call barpack module as function
-----------------------------------------------------------------------------------------------------------------------
function barpack.mt:__call(...)
return barpack.new(...)
end
return setmetatable(barpack, barpack.mt)

View File

@ -0,0 +1,158 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat textbox widget --
-----------------------------------------------------------------------------------------------------------------------
-- Custom textbox for desktop widgets
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local wibox = require("wibox")
local color = require("gears.color")
local beautiful = require("beautiful")
local redutil = require("redflat.util")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local textbox = { mt = {} }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
width = nil,
height = nil,
draw = "by_left",
separator = '%s',
color = "#404040",
font = { font = "Sans", size = 20, face = 0, slant = 0 }
}
return redutil.table.merge(style, redutil.table.check(beautiful, "desktop.common.textbox") or {})
end
-- Text alignment functions
-----------------------------------------------------------------------------------------------------------------------
local align = {}
function align.by_left(cr, _, _, text)
local ext = cr:text_extents(text)
--cr:move_to(0, height)
cr:move_to(0, ext.height)
cr:show_text(text)
end
function align.by_right(cr, width, _, text)
local ext = cr:text_extents(text)
--cr:move_to(width - (ext.width + ext.x_bearing), height)
cr:move_to(width - (ext.width + ext.x_bearing), ext.height)
cr:show_text(text)
end
function align.by_edges(cr, width, height, text, style)
local left_text, right_text = string.match(text, "(.+)" .. style.separator .. "(.+)")
if left_text and right_text then
align.by_left(cr, width, height, left_text)
align.by_right(cr, width, height, right_text)
else
align.by_right(cr, width, height, text)
end
end
function align.by_width(cr, width, _, text)
local ext = cr:text_extents(text)
local text_gap = (width - ext.width - ext.x_bearing)/(#text - 1)
local gap = 0
for i = 1, #text do
local c = string.sub(text, i, i)
--cr:move_to(gap, height)
cr:move_to(gap, ext.height)
cr:show_text(c)
local c_ext = cr:text_extents(c)
gap = gap + text_gap + c_ext.width + c_ext.x_bearing
-- !!! WORKAROUND for space character width only for font size = 24 font = "Play bold" !!!
--if c == " " then
-- gap = gap + 6
--end
end
end
-- Create a new textbox widget
-- @param txt Text to display
-- @param style.text Table containing font parameters
-- @param style.color Font color
-- @param style.draw Text align method
-- @param style.width Widget width (optional)
-- @param style.height Widget height (optional)
-----------------------------------------------------------------------------------------------------------------------
function textbox.new(txt, style)
-- Initialize vars
--------------------------------------------------------------------------------
style = redutil.table.merge(default_style(), style or {})
-- local textdraw = align[style.draw] or align.by_left
-- Create custom widget
--------------------------------------------------------------------------------
local textwidg = wibox.widget.base.make_widget()
textwidg._data = {
text = txt or "",
width = style.width,
color = style.color
}
-- User functions
------------------------------------------------------------
function textwidg:set_text(text)
if self._data.text ~= text then
self._data.text = text
self:emit_signal("widget::redraw_needed")
end
end
function textwidg:set_color(value)
if self._data.color ~= value then
self._data.color = value
self:emit_signal("widget::redraw_needed")
end
end
function textwidg:set_width(width)
if self._data.width ~= width then
self._data.width = width
self:emit_signal("widget::redraw_needed")
end
end
-- Fit
------------------------------------------------------------
function textwidg:fit(_, width, height)
local w = self._data.width and math.min(self._data.width, width) or width
local h = style.height and math.min(style.height, height) or height
return w, h
end
-- Draw
------------------------------------------------------------
function textwidg:draw(_, cr, width, height)
cr:set_source(color(self._data.color))
redutil.cairo.set_font(cr, style.font)
align[style.draw](cr, width, height, self._data.text, style)
end
--------------------------------------------------------------------------------
return textwidg
end
-- Config metatable to call textbox module as function
-----------------------------------------------------------------------------------------------------------------------
function textbox.mt:__call(...)
return textbox.new(...)
end
return setmetatable(textbox, textbox.mt)

View File

@ -0,0 +1,10 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat library --
-----------------------------------------------------------------------------------------------------------------------
local wrequire = require("redflat.util").wrequire
local setmetatable = setmetatable
local lib = { _NAME = "redflat.desktop" }
return setmetatable(lib, { __index = wrequire })

View File

@ -0,0 +1,136 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat dashpack desktop widget --
-----------------------------------------------------------------------------------------------------------------------
-- Multi monitoring widget
-- Several lines with progressbar, label and text
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local string = string
local unpack = unpack or table.unpack
local wibox = require("wibox")
local beautiful = require("beautiful")
local timer = require("gears.timer")
local redutil = require("redflat.util")
local svgbox = require("redflat.gauge.svgbox")
local lines = require("redflat.desktop.common.pack.lines")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local dashpack = { mt = {} }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
icon = { image = nil, margin = { 0, 0, 0, 0 } },
lines = {},
margin = { 0, 0, 0, 0 },
digits = 3,
dislabel = "OFF",
unit = { { "B", -1 }, { "KB", 1024 }, { "MB", 1024^2 }, { "GB", 1024^3 } },
color = { main = "#b1222b", wibox = "#161616", gray = "#404040" }
}
return redutil.table.merge(style, redutil.table.check(beautiful, "desktop.multiline") or {})
end
local default_args = { timeout = 60, sensors = {} }
-- Create a new widget
-----------------------------------------------------------------------------------------------------------------------
function dashpack.new(args, style)
-- Initialize vars
--------------------------------------------------------------------------------
local dwidget = {}
args = redutil.table.merge(default_args, args or {})
style = redutil.table.merge(default_style(), style or {})
local alert_data = { counter = 0, state = false }
dwidget.style = style
-- initialize progressbar lines
local lines_style = redutil.table.merge(style.lines, { color = style.color })
local pack = lines(#args.sensors, lines_style)
-- add icon if needed
if style.icon.image then
dwidget.icon = svgbox(style.icon.image)
dwidget.icon:set_color(style.color.gray)
dwidget.area = wibox.layout.align.horizontal()
dwidget.area:set_middle(wibox.container.margin(pack.layout, unpack(style.margin)))
dwidget.area:set_left(wibox.container.margin(dwidget.icon, unpack(style.icon.margin)))
else
dwidget.area = wibox.container.margin(pack.layout, unpack(style.margin))
end
for i, sensor in ipairs(args.sensors) do
if sensor.name then pack:set_label(string.upper(sensor.name), i) end
end
-- Update info function
--------------------------------------------------------------------------------
local function set_raw_state(state, maxm, crit, i)
local alert = crit and state[1] > crit
local text_color = alert and style.color.main or style.color.gray
pack:set_values(state[1] / maxm, i)
pack:set_label_color(text_color, i)
if style.lines.show.text or style.lines.show.tooltip then
local txt = state.off and style.dislabel
or redutil.text.dformat(state[2] or state[1], style.unit, style.digits)
pack:set_text(txt, i)
pack:set_text_color(text_color, i)
end
if style.icon.image then
alert_data.counter = alert_data.counter + 1
alert_data.state = alert_data.state or alert
if alert_data.counter == #args.sensors then
dwidget.icon:set_color(alert_data.state and style.color.main or style.color.gray)
end
end
end
local function line_hadnler(maxm, crit, i)
return function(state) set_raw_state(state, maxm, crit, i) end
end
local function update()
alert_data = { counter = 0, state = false }
--if style.icon.image then dwidget.icon:set_color(style.color.gray) end
for i, sens in ipairs(args.sensors) do
local maxm, crit = sens.maxm, sens.crit
if sens.meter_function then
local state = sens.meter_function(sens.args)
set_raw_state(state, maxm, crit, i)
else
sens.async_function(line_hadnler(maxm, crit, i))
end
end
end
-- Set update timer
--------------------------------------------------------------------------------
local t = timer({ timeout = args.timeout })
t:connect_signal("timeout", update)
t:start()
t:emit_signal("timeout")
--------------------------------------------------------------------------------
return dwidget
end
-- Config metatable to call module as function
-----------------------------------------------------------------------------------------------------------------------
function dashpack.mt:__call(...)
return dashpack.new(...)
end
return setmetatable(dashpack, dashpack.mt)

View File

@ -0,0 +1,167 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat multi monitoring deskotp widget --
-----------------------------------------------------------------------------------------------------------------------
-- Multi monitoring widget
-- Pack of vertical indicators and two lines with labeled progressbar
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
--local awful = require("awful")
local wibox = require("wibox")
local beautiful = require("beautiful")
local timer = require("gears.timer")
local unpack = unpack or table.unpack
local dcommon = require("redflat.desktop.common")
local redutil = require("redflat.util")
local svgbox = require("redflat.gauge.svgbox")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local multim = { mt = {} }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
lines = {},
upbar = { width = 40 },
digits = 3,
height = { upright = 100, lines = 60 },
icon = { image = nil, margin = { 0, 20, 0, 0 }, full = false },
labels = {},
unit = { { "MB", - 1 }, { "GB", 1024 } },
color = { main = "#b1222b", wibox = "#161616", gray = "#404040" }
}
return redutil.table.merge(style, redutil.table.check(beautiful, "desktop.multimeter") or {})
end
local default_args = {
topbars = { num = 1, maxm = 1},
lines = { maxm = 1 },
meter = {},
timeout = 60,
}
-- Support functions
-----------------------------------------------------------------------------------------------------------------------
local function set_info(value, args, upright, lines, icon, last, style)
local upright_alert = value.alert
-- set progressbar values and color
for i, line in ipairs(args.lines) do
lines:set_values(value.lines[i][1] / line.maxm, i)
lines:set_text(redutil.text.dformat(value.lines[i][2], line.unit or style.unit, style.digits), i)
if line.crit then
local cc = value.lines[i][1] > line.crit and style.color.main or style.color.gray
lines:set_text_color(cc, i)
if style.labels[i] then lines:set_label_color(cc, i) end
end
end
-- set upright value
for i = 1, args.topbars.num do
local v = value.bars[i] and value.bars[i].value or 0
local tip = value.bars[i] and value.bars[i].text or nil
upright:set_values(v / args.topbars.maxm, i, tip)
if args.topbars.crit then upright_alert = upright_alert or v > args.topbars.crit end
end
-- colorize icon if needed
if style.icon.image and upright_alert ~= last.alert then
icon:set_color(upright_alert and style.color.main or style.color.gray)
last.alert = upright_alert
end
end
-- Create a new widget
-----------------------------------------------------------------------------------------------------------------------
function multim.new(args, style)
-- Initialize vars
--------------------------------------------------------------------------------
local dwidget = {}
local icon
local last = { alert = false }
args = redutil.table.merge(default_args, args or {})
--local geometry = redutil.table.merge(default_geometry, geometry or {})
style = redutil.table.merge(default_style(), style or {})
local lines_style = redutil.table.merge(style.lines, { progressbar = { color = style.color } })
local upbar_style = redutil.table.merge(style.upbar, { color = style.color })
dwidget.style = style
-- Construct layouts
--------------------------------------------------------------------------------
local lines = dcommon.pack.lines(#args.lines, lines_style)
local upright = dcommon.pack.upright(args.topbars.num, upbar_style)
lines.layout:set_forced_height(style.height.lines)
if style.icon.image then
icon = svgbox(style.icon.image)
icon:set_color(style.color.gray)
end
dwidget.area = wibox.widget({
{
icon and not style.icon.full and wibox.container.margin(icon, unpack(style.icon.margin)),
upright.layout,
nil,
forced_height = style.height.upright,
layout = wibox.layout.align.horizontal
},
nil,
lines.layout,
layout = wibox.layout.align.vertical
})
if icon and style.icon.full then
dwidget.area = wibox.widget({
wibox.container.margin(icon, unpack(style.icon.margin)),
dwidget.area,
nil,
layout = wibox.layout.align.horizontal
})
end
for i, label in ipairs(style.labels) do
lines:set_label(label, i)
end
-- Update info function
--------------------------------------------------------------------------------
local function raw_set(state)
set_info(state, args, upright, lines, icon, last, style)
end
local function update_plain()
local state = args.meter.func(args.meter.args)
set_info(state, args, upright, lines, icon, last, style)
end
local update = args.meter.async and function() args.meter.async(raw_set, args.meter.args) end or update_plain
-- Set update timer
--------------------------------------------------------------------------------
local t = timer({ timeout = args.timeout })
t:connect_signal("timeout", update)
t:start()
t:emit_signal("timeout")
--------------------------------------------------------------------------------
return dwidget
end
-- Config metatable to call module as function
-----------------------------------------------------------------------------------------------------------------------
function multim.mt:__call(...)
return multim.new(...)
end
return setmetatable(multim, multim.mt)

View File

@ -0,0 +1,138 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat simple line desktop widget --
-----------------------------------------------------------------------------------------------------------------------
-- Multi monitoring widget
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local string = string
local wibox = require("wibox")
local beautiful = require("beautiful")
local timer = require("gears.timer")
local redutil = require("redflat.util")
local svgbox = require("redflat.gauge.svgbox")
local textbox = require("redflat.desktop.common.textbox")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local sline = { mt = {} }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
lbox = { draw = "by_left", width = 50 },
rbox = { draw = "by_right", width = 50 },
digits = 3,
icon = nil,
iwidth = 120,
unit = { { "B", -1 }, { "KB", 1024 }, { "MB", 1024^2 }, { "GB", 1024^3 } },
color = { main = "#b1222b", wibox = "#161616", gray = "#404040" }
}
return redutil.table.merge(style, redutil.table.check(beautiful, "desktop.singleline") or {})
end
local default_args = { timeout = 60, sensors = {} }
-- Create a new widget
-----------------------------------------------------------------------------------------------------------------------
function sline.new(args, style)
-- Initialize vars
--------------------------------------------------------------------------------
local dwidget = {}
args = redutil.table.merge(default_args, args or {})
style = redutil.table.merge(default_style(), style or {})
dwidget.style = style
-- Initialize layouts
--------------------------------------------------------------------------------
dwidget.item = {}
dwidget.icon = {}
dwidget.area = wibox.layout.align.horizontal()
local mid = wibox.layout.flex.horizontal()
-- construct line
for i, _ in ipairs(args.sensors) do
dwidget.item[i] = textbox("", style.rbox)
if style.icon then dwidget.icon[i] = svgbox(style.icon) end
local boxlayout = wibox.widget({
textbox(string.upper(args.sensors[i].name or "mon"), style.lbox),
style.icon and {
nil, dwidget.icon[i], nil,
expand = "outside",
layout = wibox.layout.align.horizontal
},
dwidget.item[i],
forced_width = style.iwidth,
layout = wibox.layout.align.horizontal
})
if i == 1 then
dwidget.area:set_left(boxlayout)
else
local space = wibox.layout.align.horizontal()
space:set_right(boxlayout)
mid:add(space)
end
end
dwidget.area:set_middle(mid)
-- Update info function
--------------------------------------------------------------------------------
local function set_raw_state(state, crit, i)
local text_color = crit and state[1] > crit and style.color.main or style.color.gray
local txt = redutil.text.dformat(state[2] or state[1], style.unit, style.digits)
dwidget.item[i]:set_text(txt)
dwidget.item[i]:set_color(text_color)
if dwidget.icon[i] then
local icon_color = state.off and style.color.gray or style.color.main
dwidget.icon[i]:set_color(icon_color)
end
end
local function item_hadnler(crit, i)
return function(state) set_raw_state(state, crit, i) end
end
local function update()
for i, sens in ipairs(args.sensors) do
local crit = sens.crit
if sens.meter_function then
local state = sens.meter_function(sens.args)
set_raw_state(state, crit, i)
else
sens.async_function(item_hadnler(crit, i))
end
end
end
-- Set update timer
--------------------------------------------------------------------------------
local t = timer({ timeout = args.timeout })
t:connect_signal("timeout", update)
t:start()
t:emit_signal("timeout")
--------------------------------------------------------------------------------
return dwidget
end
-- Config metatable to call module as function
-----------------------------------------------------------------------------------------------------------------------
function sline.mt:__call(...)
return sline.new(...)
end
return setmetatable(sline, sline.mt)

View File

@ -0,0 +1,173 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat speed meter deskotp widget --
-----------------------------------------------------------------------------------------------------------------------
-- Network or disk i/o speed indicators
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local wibox = require("wibox")
local beautiful = require("beautiful")
local timer = require("gears.timer")
local unpack = unpack or table.unpack
local system = require("redflat.system")
local redutil = require("redflat.util")
local dcommon = require("redflat.desktop.common")
local svgbox = require("redflat.gauge.svgbox")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local speedmeter = { mt = {} }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
icon = { margin = { 0, 0, 0, 0 }, },
label = { width = 100 },
margins = { label = { 0, 0, 0, 0 }, chart = { 0, 0, 2, 2 } },
progressbar = { chunk = { width = 10, gap = 5 }, height = 4 },
chart = {},
height = { chart = 50 },
digits = 2,
unit = { { "B", -1 }, { "KB", 1024 }, { "MB", 1024^2 }, { "GB", 1024^3 } },
color = { main = "#b1222b", wibox = "#161616", gray = "#404040" }
}
return redutil.table.merge(style, redutil.table.check(beautiful, "desktop.speedmeter.compact") or {})
end
local default_args = {
autoscale = true,
label = "NETWORK",
timeout = 5,
interface = "eth0",
meter_function = system.net_speed
}
local default_maxspeed = { up = 10 * 1024, down = 10 * 1024 }
-- Support functions
-----------------------------------------------------------------------------------------------------------------------
-- Construct chart with support elements
--------------------------------------------------------------------------------
local function value_chart(style, image, maxspeed)
local chart = dcommon.chart(redutil.table.merge(style.chart, { maxm = maxspeed }))
local progressbar = dcommon.bar.plain(redutil.table.merge(style.progressbar, { maxm = maxspeed }))
local text = dcommon.textbox("", style.label)
local icon = image and svgbox(image) or nil
local area = wibox.widget({
progressbar,
{
{
nil,
wibox.container.margin(text, unpack(style.margins.label)),
nil,
expand = "outside",
layout = wibox.layout.align.vertical
},
wibox.container.margin(chart, unpack(style.margins.chart)),
nil,
layout = wibox.layout.align.horizontal
},
progressbar,
forced_height = style.height.chart,
layout = wibox.layout.align.vertical
})
if image then
icon:set_color(style.color.gray)
area = wibox.widget({
wibox.container.margin(icon, unpack(style.icon.margin)),
area,
nil,
forced_height = style.height.chart,
layout = wibox.layout.align.horizontal
})
end
return { chart = chart, progressbar = progressbar, text = text, icon = icon, area = area }
end
-- Create a new speed meter widget
-----------------------------------------------------------------------------------------------------------------------
function speedmeter.new(args, style)
-- Initialize vars
--------------------------------------------------------------------------------
local dwidget = {}
local storage = {}
local last_state = { false, false }
args = redutil.table.merge(default_args, args or {})
style = redutil.table.merge(default_style(), style or {})
local maxspeed = redutil.table.merge(default_maxspeed, args.maxspeed or {})
style.chart = redutil.table.merge(style.chart, { autoscale = args.autoscale, color = style.color.gray })
style.progressbar = redutil.table.merge(style.progressbar, { autoscale = args.autoscale, color = style.color })
style.label = redutil.table.merge(style.label, { draw = "by_edges", color = style.color.gray })
dwidget.style = style
-- Construct indicators
--------------------------------------------------------------------------------
local widg = { {}, {} }
local placement = { "up", "down" }
for i = 1, 2 do
widg[i] = value_chart(style, style.icon[placement[i]], maxspeed[placement[i]])
end
dwidget.area = wibox.widget({
widg[1].area, nil, widg[2].area,
layout = wibox.layout.align.vertical
})
-- Update info
--------------------------------------------------------------------------------
local function update()
local state = args.meter_function(args.interface, storage)
for i = 1, 2 do
widg[i].chart:set_value(state[i])
widg[i].progressbar:set_value(state[i])
widg[i].text:set_text(redutil.text.dformat(state[i], style.unit, style.digits))
if widg[i].icon and args.crit then
local st = state[i] > args.crit[placement[i]]
if st ~= last_state[i] then
local newc = st and style.color.main or style.color.gray
widg[i].icon:set_color(newc)
widg[i].text:set_color(newc)
last_state[i] = st
end
end
end
end
-- Set update timer
--------------------------------------------------------------------------------
local t = timer({ timeout = args.timeout })
t:connect_signal("timeout", update)
t:start()
t:emit_signal("timeout")
--------------------------------------------------------------------------------
return dwidget
end
-- Config metatable to call module as function
-----------------------------------------------------------------------------------------------------------------------
function speedmeter.mt:__call(...)
return speedmeter.new(...)
end
return setmetatable(speedmeter, speedmeter.mt)

View File

@ -0,0 +1,10 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat library --
-----------------------------------------------------------------------------------------------------------------------
local wrequire = require("redflat.util").wrequire
local setmetatable = setmetatable
local lib = { _NAME = "redflat.desktop.speedmeter" }
return setmetatable(lib, { __index = wrequire })

View File

@ -0,0 +1,199 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat speed meter deskotp widget --
-----------------------------------------------------------------------------------------------------------------------
-- Network or disk i/o speed indicators
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local wibox = require("wibox")
local beautiful = require("beautiful")
local timer = require("gears.timer")
local system = require("redflat.system")
local redutil = require("redflat.util")
local dcommon = require("redflat.desktop.common")
local svgbox = require("redflat.gauge.svgbox")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local speedmeter = { mt = {} }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
images = {},
label = { height = 20, separator = "^" },
progressbar = { chunk = { width = 10, gap = 5 }, height = 4 },
chart = {},
barvalue_height = 32,
fullchart_height = 78,
digits = 2,
image_gap = 20,
unit = { { "B", -1 }, { "KB", 1024 }, { "MB", 1024^2 }, { "GB", 1024^3 } },
color = { main = "#b1222b", wibox = "#161616", gray = "#404040" }
}
return redutil.table.merge(style, redutil.table.check(beautiful, "desktop.speedmeter.normal") or {})
end
local default_args = {
autoscale = true,
label = "NETWORK",
timeout = 5,
interface = "eth0",
meter_function = system.net_speed
}
local default_maxspeed = { up = 10 * 1024, down = 10 * 1024 }
-- Support functions
-----------------------------------------------------------------------------------------------------------------------
local function set_fullchart_info(objects, label, state, style)
for i, o in ipairs(objects) do
o.barvalue:set_value(state[i])
o.barvalue:set_text(
label .. style.label.separator .. redutil.text.dformat(state[i], style.unit, style.digits, " ")
)
o.chart:set_value(state[i])
end
end
local function colorize_icon(objects, last_state, values, crit, style)
for i, o in ipairs(objects) do
local st = values[i] > crit[i]
if st ~= last_state[i] then
o:set_color(st and style.color.main or style.color.gray)
last_state[i] = st
end
end
end
-- Construct complex indicator with progress bar and label on top of it
--------------------------------------------------------------------------------
local function barvalue(progressbar_style, label_style)
local widg = {}
-- construct layout with indicators
local progressbar = dcommon.bar.plain(progressbar_style)
local label = dcommon.textbox(nil, label_style)
widg.layout = wibox.widget({
label, nil, progressbar,
layout = wibox.layout.align.vertical,
})
-- setup functions
function widg:set_text(text) label:set_text(text) end
function widg:set_value(x) progressbar:set_value(x) end
return widg
end
-- Construct complex indicator with history chart, progress bar and label
--------------------------------------------------------------------------------
local function fullchart(label_style, progressbar_style, chart_style, barvalue_height, maxm)
local widg = {}
chart_style = redutil.table.merge(chart_style, { maxm = maxm })
progressbar_style = redutil.table.merge(progressbar_style, { maxm = maxm })
-- construct layout with indicators
widg.barvalue = barvalue(progressbar_style, label_style)
widg.chart = dcommon.chart(chart_style)
widg.barvalue.layout:set_forced_height(barvalue_height)
widg.layout = wibox.widget({
widg.barvalue.layout, nil, widg.chart,
layout = wibox.layout.align.vertical,
})
return widg
end
-- Construct speed info elements (fullchart and icon in one layout)
--------------------------------------------------------------------------------
local function speed_line(image, maxm, el_style, style)
local fc = fullchart(el_style.label, el_style.progressbar, el_style.chart, style.barvalue_height, maxm)
local align = wibox.layout.align.horizontal()
local icon
align:set_right(fc.layout)
align:set_forced_height(style.fullchart_height)
if image then
icon = svgbox(image)
icon:set_color(style.color.gray)
align:set_left(wibox.container.margin(icon, 0, style.image_gap))
end
return fc, align, icon
end
-- Create a new speed meter widget
-----------------------------------------------------------------------------------------------------------------------
function speedmeter.new(args, style)
-- Initialize vars
--------------------------------------------------------------------------------
local dwidget = {}
local storage = {}
local last = {}
args = redutil.table.merge(default_args, args or {})
style = redutil.table.merge(default_style(), style or {})
local maxspeed = redutil.table.merge(default_maxspeed, args.maxspeed or {})
local elements_style = {
label = redutil.table.merge(style.label, { draw = "by_edges", color = style.color.gray }),
progressbar = redutil.table.merge(style.progressbar, { autoscale = args.autoscale, color = style.color }),
chart = redutil.table.merge(style.chart, { autoscale = args.autoscale, color = style.color.gray })
}
dwidget.style = style
-- Construct indicators
--------------------------------------------------------------------------------
local up_widget, up_layout, up_icon = speed_line(style.images[1], maxspeed.up, elements_style, style)
local down_widget, down_layout, down_icon = speed_line(style.images[2], maxspeed.down, elements_style, style)
dwidget.area = wibox.widget({
up_layout, nil, down_layout,
layout = wibox.layout.align.vertical
})
-- Update info
--------------------------------------------------------------------------------
local function update()
local state = args.meter_function(args.interface, storage)
set_fullchart_info({ up_widget, down_widget }, args.label, state, style)
if style.images and args.crit then
colorize_icon({ up_icon, down_icon }, last, state, { args.crit.up, args.crit.down }, style)
end
end
-- Set update timer
--------------------------------------------------------------------------------
local t = timer({ timeout = args.timeout })
t:connect_signal("timeout", update)
t:start()
t:emit_signal("timeout")
--------------------------------------------------------------------------------
return dwidget
end
-- Config metatable to call module as function
-----------------------------------------------------------------------------------------------------------------------
function speedmeter.mt:__call(...)
return speedmeter.new(...)
end
return setmetatable(speedmeter, speedmeter.mt)

View File

@ -0,0 +1,98 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat desktop text widget --
-----------------------------------------------------------------------------------------------------------------------
-- Text widget with text update function
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local textbox = require("wibox.widget.textbox")
local beautiful = require("beautiful")
local timer = require("gears.timer")
local lgi = require("lgi")
local Pango = lgi.Pango
local redutil = require("redflat.util")
-- Initialize tables and vars for module
-----------------------------------------------------------------------------------------------------------------------
local textset = { mt = {} }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
font = "Sans 12",
spacing = 0,
color = { gray = "#525252" }
}
return redutil.table.merge(style, redutil.table.check(beautiful, "desktop.textset") or {})
end
-- Create a textset widget. It draws the time it is in a textbox.
-- @param format The time format. Default is " %a %b %d, %H:%M ".
-- @param timeout How often update the time. Default is 60.
-- @return A textbox widget
-----------------------------------------------------------------------------------------------------------------------
function textset.new(args, style)
-- Initialize vars
--------------------------------------------------------------------------------
args = args or {}
--local funcarg = args.arg or {}
--local timeout = args.timeout or { 60 }
--local actions = args.actions or {}
--local async = args.async or {}
style = redutil.table.merge(default_style(), style or {})
-- Create widget
--------------------------------------------------------------------------------
local widg = textbox()
widg:set_font(style.font)
widg:set_valign("top")
-- Some pivate properties of awesome textbox v4.0
widg._private.layout:set_justify(true)
widg._private.layout:set_spacing(Pango.units_from_double(style.spacing))
-- data setup
local data = {}
local timers = {}
for i = 1, #args do data[i] = "" end
-- update info function
local function update()
local state = {}
for _, txt in ipairs(data) do state[#state + 1] = txt end
widg:set_markup(string.format('<span color="%s">%s</span>', style.color.gray, table.concat(state)))
end
-- Set update timers
--------------------------------------------------------------------------------
for i, block in ipairs(args) do
timers[i] = timer({ timeout = block.timeout or args[1].timeout })
if block.async then
timers[i]:connect_signal("timeout", function()
block.async(function(state) data[i] = block.action(state); update() end)
end)
else
timers[i]:connect_signal("timeout", function()
data[i] = block.action(); update()
end)
end
timers[i]:start()
timers[i]:emit_signal("timeout")
end
--------------------------------------------------------------------------------
return widg
end
-- Config metatable to call textset module as function
-----------------------------------------------------------------------------------------------------------------------
function textset.mt:__call(...)
return textset.new(...)
end
return setmetatable(textset, textset.mt)

View File

@ -0,0 +1,367 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat application launcher widget --
-----------------------------------------------------------------------------------------------------------------------
-- Widget with application list and quick search
-- Application list generated by redflat.dfparser
-----------------------------------------------------------------------------------------------------------------------
-- Some code was taken from
------ awful.menubar v3.5.2
------ (c) 2009, 2011-2012 Antonio Terceiro, Alexander Yakushev
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local unpack = unpack or table.unpack
local awful = require("awful")
local beautiful = require("beautiful")
local wibox = require("wibox")
local svgbox = require("redflat.gauge.svgbox")
local dfparser = require("redflat.service.dfparser")
local redutil = require("redflat.util")
local decoration = require("redflat.float.decoration")
local redtip = require("redflat.float.hotkeys")
-- Initialize tables and vars for module
-----------------------------------------------------------------------------------------------------------------------
local apprunner = { applist = {}, command = "", keys = {} }
local programs = {}
local lastquery
-- key bindings
apprunner.keys.move = {
{
{}, "Down", function() apprunner:down() end,
{ description = "Select next item", group = "Navigation" }
},
{
{}, "Up", function() apprunner:up() end,
{ description = "Select previous item", group = "Navigation" }
},
}
apprunner.keys.action = {
{
{ "Mod4" }, "F1", function() redtip:show() end,
{ description = "Show hotkeys helper", group = "Action" }
},
-- fake keys used for hotkeys helper
{
{}, "Enter", nil,
{ description = "Activate item", group = "Action" }
},
{
{}, "Escape", nil,
{ description = "Close widget", group = "Action" }
},
}
apprunner.keys.all = awful.util.table.join(apprunner.keys.move, apprunner.keys.action)
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
itemnum = 5,
geometry = { width = 620, height = 520 },
border_margin = { 10, 10, 10, 10 },
title_height = 48,
prompt_height = 35,
title_icon = nil,
icon_margin = { 8, 12, 0, 0 },
parser = {},
list_text_vgap = 4,
list_icon_margin = { 6, 12, 6, 6 },
name_font = "Sans 12",
comment_font = "Sans 12",
border_width = 2,
keytip = { geometry = { width = 400 } },
dimage = redutil.base.placeholder(),
color = { border = "#575757", text = "#aaaaaa", highlight = "#eeeeee", main = "#b1222b",
bg = "#161616", bg_second = "#181818", wibox = "#202020", icon = "a0a0a0" },
shape = nil
}
return redutil.table.merge(style, redutil.table.check(beautiful, "float.apprunner") or {})
end
-- Support functions
-----------------------------------------------------------------------------------------------------------------------
-- Fuction to build list item
--------------------------------------------------------------------------------
local function construct_item(style)
local item = {
icon = wibox.widget.imagebox(),
name = wibox.widget.textbox(),
comment = wibox.widget.textbox(),
bg = style.color.bg,
cmd = ""
}
item.name:set_font(style.name_font)
item.comment:set_font(style.comment_font)
-- Construct item layouts
------------------------------------------------------------
local text_vertical = wibox.layout.align.vertical()
local text_horizontal = wibox.layout.align.horizontal()
text_horizontal:set_left(text_vertical)
text_vertical:set_top(wibox.container.margin(item.name, 0, 0, style.list_text_vgap))
text_vertical:set_middle(item.comment)
local item_horizontal = wibox.layout.align.horizontal()
item_horizontal:set_left(wibox.container.margin(item.icon, unpack(style.list_icon_margin)))
item_horizontal:set_middle(text_horizontal)
item.layout = wibox.container.background(item_horizontal, item.bg)
-- Item functions
------------------------------------------------------------
function item:set(args)
args = args or {}
local name_text = awful.util.escape(args.Name) or ""
item.name:set_markup(name_text)
local comment_text = args.Comment and awful.util.escape(args.Comment)
or args.Name and "No description"
or ""
item.comment:set_markup(comment_text)
item.icon:set_image(args.icon_path or style.dimage)
item.icon:set_visible((args.Name))
item.cmd = args.cmdline
end
function item:set_bg(color)
item.bg = color
item.layout:set_bg(color)
end
function item:set_select()
item.layout:set_bg(style.color.main)
item.layout:set_fg(style.color.highlight)
end
function item:set_unselect()
item.layout:set_bg(item.bg)
item.layout:set_fg(style.color.text)
end
function item:run()
awful.spawn(item.cmd)
end
------------------------------------------------------------
return item
end
-- Fuction to build application list
--------------------------------------------------------------------------------
local function construct_list(num, progs, style)
local list = { selected = 1, position = 1 }
-- Construct application list
------------------------------------------------------------
local list_layout = wibox.layout.flex.vertical()
list.layout = wibox.container.background(list_layout, style.color.bg)
list.items = {}
for i = 1, num do
list.items[i] = construct_item(style)
list.items[i]:set_bg((i % 2) == 1 and style.color.bg or style.color.bg_second)
list_layout:add(list.items[i].layout)
end
-- Application list functions
------------------------------------------------------------
function list:set_select(index)
list.items[list.selected]:set_unselect()
list.selected = index
list.items[list.selected]:set_select()
end
function list:update(t)
for i = list.position, (list.position - 1 + num) do list.items[i - list.position + 1]:set(t[i]) end
list:set_select(list.selected)
end
-- First run actions
------------------------------------------------------------
list:update(progs)
list:set_select(1)
------------------------------------------------------------
return list
end
-- Sort function
--------------------------------------------------------------------------------
local function sort_by_query(t, query)
local l = string.len(query)
local function s(a, b)
return string.lower(string.sub(a.Name, 1, l)) == query and string.lower(string.sub(b.Name, 1, l)) ~= query
end
table.sort(t, s)
end
-- Function to filter application list by quick search input
--------------------------------------------------------------------------------
local function list_filtrate(query)
if lastquery ~= query then
programs.current = {}
for _, p in ipairs(programs.all) do
if string.match(string.lower(p.Name), query) then
table.insert(programs.current, p)
end
end
sort_by_query(programs.current, query)
apprunner.applist.position = 1
apprunner.applist:update(programs.current)
apprunner.applist:set_select(1)
lastquery = query
end
end
-- Functions to navigate through application list
-----------------------------------------------------------------------------------------------------------------------
function apprunner:down()
if self.applist.selected < math.min(self.itemnum, #programs.current) then
self.applist:set_select(self.applist.selected + 1)
elseif self.applist.selected + self.applist.position - 1 < #programs.current then
self.applist.position = self.applist.position + 1
self.applist:update(programs.current)
end
end
function apprunner:up()
if self.applist.selected > 1 then
self.applist:set_select(self.applist.selected - 1)
elseif self.applist.position > 1 then
self.applist.position = self.applist.position - 1
self.applist:update(programs.current)
end
end
-- Keypress handler
-----------------------------------------------------------------------------------------------------------------------
local function keypressed_callback(mod, key)
for _, k in ipairs(apprunner.keys.all) do
if redutil.key.match_prompt(k, mod, key) and k[3] then k[3](); return true end
end
return false
end
-- Initialize apprunner widget
-----------------------------------------------------------------------------------------------------------------------
function apprunner:init()
-- Initialize vars
--------------------------------------------------------------------------------
local style = default_style()
self.itemnum = style.itemnum
self.keytip = style.keytip
-- get full application list
programs.all = dfparser.program_list(style.parser)
programs.current = awful.util.table.clone(programs.all)
-- Create quick search widget
--------------------------------------------------------------------------------
self.textbox = wibox.widget.textbox()
self.textbox:set_ellipsize("start")
self.decorated_widget = decoration.textfield(self.textbox, style.field)
-- Build application list
--------------------------------------------------------------------------------
self.applist = construct_list(apprunner.itemnum, programs.current, style)
-- Construct widget layouts
--------------------------------------------------------------------------------
local prompt_width = style.geometry.width - 2 * style.border_margin[1]
- style.title_height - style.icon_margin[1] - style.icon_margin[2]
local prompt_layout = wibox.container.constraint(self.decorated_widget, "exact", prompt_width, style.prompt_height)
local prompt_vertical = wibox.layout.align.vertical()
prompt_vertical:set_expand("outside")
prompt_vertical:set_middle(prompt_layout)
local prompt_area_horizontal = wibox.layout.align.horizontal()
local title_image = svgbox(style.title_icon)
title_image:set_color(style.color.icon)
prompt_area_horizontal:set_left(wibox.container.margin(title_image, unpack(style.icon_margin)))
prompt_area_horizontal:set_right(prompt_vertical)
local prompt_area_layout = wibox.container.constraint(prompt_area_horizontal, "exact", nil, style.title_height)
local area_vertical = wibox.layout.align.vertical()
area_vertical:set_top(prompt_area_layout)
area_vertical:set_middle(wibox.container.margin(self.applist.layout, 0, 0, style.border_margin[3]))
local area_layout = wibox.container.margin(area_vertical, unpack(style.border_margin))
-- Create floating wibox for apprunner widget
--------------------------------------------------------------------------------
self.wibox = wibox({
ontop = true,
bg = style.color.wibox,
border_width = style.border_width,
border_color = style.color.border,
shape = style.shape
})
self.wibox:set_widget(area_layout)
self.wibox:geometry(style.geometry)
end
-- Show apprunner widget
-- Wibox appears on call and hides after "enter" or "esc" pressed
-----------------------------------------------------------------------------------------------------------------------
function apprunner:show()
if not self.wibox then
self:init()
else
list_filtrate("")
self.applist:set_select(1)
end
redutil.placement.centered(self.wibox, nil, mouse.screen.workarea)
self.wibox.visible = true
redtip:set_pack("Apprunner", self.keys.all, self.keytip.column, self.keytip.geometry)
return awful.prompt.run({
prompt = "",
textbox = self.textbox,
exe_callback = function () self.applist.items[self.applist.selected]:run() end,
done_callback = function () self:hide() end,
keypressed_callback = keypressed_callback,
changed_callback = list_filtrate,
})
end
function apprunner:hide()
self.wibox.visible = false
redtip:remove_pack()
end
-- Set user hotkeys
-----------------------------------------------------------------------------------------------------------------------
function apprunner:set_keys(keys, layout)
layout = layout or "all"
if keys then
self.keys[layout] = keys
if layout ~= "all" then self.keys.all = awful.util.table.join(self.keys.move, self.keys.action) end
end
-- self.tip = awful.util.table.join(self.keys.all, self._fake_keys)
end
-- End
-----------------------------------------------------------------------------------------------------------------------
return apprunner

View File

@ -0,0 +1,459 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat appswitcher widget --
-----------------------------------------------------------------------------------------------------------------------
-- Advanced application switcher
-----------------------------------------------------------------------------------------------------------------------
-- Some code was taken from
------ Familiar Alt Tab by Joren Heit
------ https://github.com/jorenheit/awesome_alttab
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local type = type
local math = math
local unpack = unpack or table.unpack
local table = table
local awful = require("awful")
local beautiful = require("beautiful")
local wibox = require("wibox")
local timer = require("gears.timer")
local gears = require("gears")
local pixbuf
local function load_pixbuf()
local _ = require("lgi").Gdk
pixbuf = require("lgi").GdkPixbuf
end
local is_pixbuf_loaded = pcall(load_pixbuf)
local dfparser = require("redflat.service.dfparser")
local redutil = require("redflat.util")
local redtip = require("redflat.float.hotkeys")
-- Initialize tables and vars for module
-----------------------------------------------------------------------------------------------------------------------
local appswitcher = { clients_list = {}, index = 1, hotkeys = {}, svgsize = 256, keys = {} }
local cache = {}
local svgcache = {}
local _empty_surface = redutil.base.placeholder({ txt = " " })
-- key bindings
appswitcher.keys.move = {
{
{}, "Right", function() appswitcher:switch() end,
{ description = "Select next app", group = "Navigation" }
},
{
{}, "Left", function() appswitcher:switch({ reverse = true }) end,
{ description = "Select previous app", group = "Navigation" }
},
}
appswitcher.keys.action = {
{
{}, "Return", function() appswitcher:hide() end,
{ description = "Activate and exit", group = "Action" }
},
{
{ "Mod4" }, "F1", function() redtip:show() end,
{ description = "Show hotkeys helper", group = "Action" }
},
}
appswitcher.keys.all = awful.util.table.join(appswitcher.keys.move, appswitcher.keys.action)
appswitcher._fake_keys = {
{
{}, "N", nil,
{ description = "Select app by key", group = "Navigation" }
},
}
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
wibox_height = 200,
label_height = 20,
title_height = 20,
icon_size = 48,
preview_gap = 20,
preview_format = 16/10,
preview_margin = { 20, 20, 20, 20 },
border_margin = { 6, 6, 6, 6 },
border_width = 2,
parser = {},
update_timeout = 1,
min_icon_number = 4,
keytip = { geometry = { width = 400 }, exit = false },
title_font = "Sans 12",
hotkeys = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" },
font = { font = "Sans", size = 16, face = 0, slant = 0 },
color = { border = "#575757", text = "#aaaaaa", main = "#b1222b", preview_bg = "#b1222b80",
wibox = "#202020", icon = "#a0a0a0", bg = "#161616", gray = "#575757" },
shape = nil
}
return redutil.table.merge(style, redutil.table.check(beautiful, "float.appswitcher") or {})
end
-- Support functions
-----------------------------------------------------------------------------------------------------------------------
-- Create icon visual for client
--------------------------------------------------------------------------------
local function get_icon_visual(icon_db, c, size)
local surface, buf
if icon_db[string.lower(c.class)] then
local icon = icon_db[string.lower(c.class)]
if type(icon) == "string" and string.match(icon, "%.svg") and is_pixbuf_loaded then
if svgcache[icon] then
buf = svgcache[icon]
else
buf = pixbuf.Pixbuf.new_from_file_at_scale(icon, size, size, true)
svgcache[icon] = buf
end
else
surface = gears.surface(icon)
end
else
surface = c.icon and gears.surface(c.icon) or _empty_surface
end
return surface, buf
end
-- Find all clients to be shown
--------------------------------------------------------------------------------
local function clients_find(filter)
local clients = {}
for _, c in ipairs(client.get()) do
if not (c.skip_taskbar or c.hidden or c.type == "splash" or c.type == "dock" or c.type == "desktop")
and filter(c, mouse.screen) then
table.insert(clients, c)
end
end
return clients
end
-- Loop iterating through table
--------------------------------------------------------------------------------
local function iterate(tabl, i, diff)
local nxt = i + diff
if nxt > #tabl then nxt = 1
elseif nxt < 1 then nxt = #tabl end
return nxt
end
-- Select new focused window
--------------------------------------------------------------------------------
local function focus_and_raise(c)
if c.minimized then c.minimized = false end
if not c:isvisible() then
awful.tag.viewmore(c:tags(), c.screen)
end
client.focus = c
c:raise()
end
-- Initialize appswitcher widget
-----------------------------------------------------------------------------------------------------------------------
function appswitcher:init()
-- Initialize style vars
--------------------------------------------------------------------------------
local style = default_style()
local icon_db = dfparser.icon_list(style.parser)
local iscf = 1 -- icon size correction factor
self.keytip = style.keytip
self._fake_keys[1][4].keyset = style.hotkeys
self:set_keys()
-- Create floating wibox for appswitcher widget
--------------------------------------------------------------------------------
self.wibox = wibox({
ontop = true,
bg = style.color.wibox,
border_width = style.border_width,
border_color = style.color.border,
shape = style.shape
})
-- Keygrabber
--------------------------------------------------------------------------------
local function focus_by_key(key)
self:switch({ index = awful.util.table.hasitem(style.hotkeys, key) })
end
self.keygrabber = function(mod, key, event)
if event == "press" then return false end
for _, k in ipairs(self.keys.all) do
if redutil.key.match_grabber(k, mod, key) then k[3](); return false end
end
if awful.util.table.hasitem(style.hotkeys, key) then focus_by_key(key) end
end
-- Function to form title string for given client (name and tags)
--------------------------------------------------------------------------------
function self.title_generator(c)
local client_tags = {}
for _, t in ipairs(c:tags()) do
client_tags[#client_tags + 1] = string.upper(t.name)
end
local tag_text = string.format('<span color="%s">[%s]</span>', style.color.gray, table.concat(client_tags, " "))
return string.format("%s %s", awful.util.escape(c.name) or "Untitled", tag_text)
end
-- Function to correct wibox size for given namber of icons
--------------------------------------------------------------------------------
function self.size_correction(inum)
local w, h
inum = math.max(inum, style.min_icon_number)
local expen_h = (inum - 1) * style.preview_gap + style.preview_margin[1] + style.preview_margin[2]
+ style.border_margin[1] + style.border_margin[2]
local expen_v = style.label_height + style.preview_margin[3] + style.preview_margin[4] + style.title_height
+ style.border_margin[3] + style.border_margin[4]
-- calculate width
local widget_height = style.wibox_height - expen_v + style.label_height
local max_width = screen[mouse.screen].geometry.width - 2 * self.wibox.border_width
local wanted_width = inum * ((widget_height - style.label_height) * style.preview_format) + expen_h
-- check if need size correction
if wanted_width <= max_width then
-- correct width
w = wanted_width
h = style.wibox_height
iscf = 1
else
-- correct height
local wanted_widget_width = (max_width - expen_h) / inum
local corrected_height = wanted_widget_width / style.preview_format + expen_v
w = max_width
h = corrected_height
iscf = (corrected_height - expen_v) / (style.wibox_height - expen_v)
end
-- set wibox size
self.wibox:geometry({ width = w, height = h })
end
-- Create custom widget to draw previews
--------------------------------------------------------------------------------
local widg = wibox.widget.base.make_widget()
-- Fit
------------------------------------------------------------
function widg:fit(_, width, height) return width, height end
-- Draw
------------------------------------------------------------
function widg.draw(_, _, cr, _, height)
-- calculate preview pattern size
local psize = {
width = (height - style.label_height) * style.preview_format,
height = (height - style.label_height)
}
-- Shift pack of preview icons to center of widget if needed
if #self.clients_list < style.min_icon_number then
local tr = (style.min_icon_number - #self.clients_list) * (style.preview_gap + psize.width) / 2
cr:translate(tr, 0)
end
-- draw all previews
for i = 1, #self.clients_list do
-- support vars
local sc, tr, surface, pixbuf_
local c = self.clients_list[i]
-- create surface and calculate scale and translate factors
if c:isvisible() then
surface = gears.surface(c.content)
local cg = c:geometry()
if cg.width/cg.height > style.preview_format then
sc = psize.width / cg.width
tr = {0, (psize.height - sc * cg.height) / 2}
else
sc = psize.height / cg.height
tr = {(psize.width - sc * cg.width) / 2, 0}
end
else
-- surface = gears.surface(icon_db[string.lower(c.class)] or c.icon)
surface, pixbuf_ = get_icon_visual(icon_db, c, self.svgsize)
-- sc = style.icon_size / surface.width * iscf
sc = style.icon_size / (surface and surface.width or pixbuf_.width) * iscf
tr = {(psize.width - style.icon_size * iscf) / 2, (psize.height - style.icon_size * iscf) / 2}
end
-- translate cairo for every icon
if i > 1 then cr:translate(style.preview_gap + psize.width, 0) end
-- draw background for preview
cr:set_source(gears.color(i == self.index and style.color.main or style.color.preview_bg))
cr:rectangle(0, 0, psize.width, psize.height)
cr:fill()
-- draw current preview or application icon
cr:save()
cr:translate(unpack(tr))
cr:scale(sc, sc)
if pixbuf_ then
cr:set_source_pixbuf(pixbuf_, 0, 0)
else
cr:set_source_surface(surface, 0, 0)
end
cr:paint()
cr:restore()
-- draw label
local txt = style.hotkeys[i] or "?"
cr:set_source(gears.color(i == self.index and style.color.main or style.color.text))
redutil.cairo.set_font(cr, style.font)
redutil.cairo.textcentre.horizontal(cr, { psize.width/2, psize.height + style.label_height }, txt)
end
collectgarbage() -- prevents memory leak after complex draw function
end
-- Set widget and create title for wibox
--------------------------------------------------------------------------------
self.widget = widg
self.titlebox = wibox.widget.textbox("TEXT")
self.titlebox:set_align("center")
self.titlebox:set_font(style.title_font)
local title_layout = wibox.container.constraint(self.titlebox, "exact", nil, style.title_height)
local vertical_layout = wibox.layout.fixed.vertical()
local widget_bg = wibox.container.background(
wibox.container.margin(self.widget, unpack(style.preview_margin)),
style.color.bg
)
vertical_layout:add(title_layout)
vertical_layout:add(widget_bg)
self.wibox:set_widget(wibox.container.margin(vertical_layout, unpack(style.border_margin)))
-- Set preview icons update timer
--------------------------------------------------------------------------------
self.update_timer = timer({ timeout = style.update_timeout })
self.update_timer:connect_signal("timeout", function() self.widget:emit_signal("widget::redraw_needed") end)
-- Restart switcher if any client was closed
--------------------------------------------------------------------------------
client.connect_signal("unmanage",
function(c)
if self.wibox.visible and awful.util.table.hasitem(self.clients_list, c) then
self:hide(true)
self:show(cache.args)
end
end
)
end
-- Show appswitcher widget
-----------------------------------------------------------------------------------------------------------------------
function appswitcher:show(args)
args = args or {}
local filter = args.filter
local noaction = args.noaction
if not self.wibox then self:init() end
if self.wibox.visible then
self:switch(args)
return
end
local clients = clients_find(filter)
if #clients == 0 then return end
self.clients_list = clients
cache.args = args
self.size_correction(#clients)
redutil.placement.centered(self.wibox, nil, mouse.screen.workarea)
self.update_timer:start()
awful.keygrabber.run(self.keygrabber)
self.index = awful.util.table.hasitem(self.clients_list, client.focus) or 1
self.titlebox:set_markup(self.title_generator(self.clients_list[self.index]))
if not noaction then self:switch(args) end
self.widget:emit_signal("widget::redraw_needed")
self.wibox.visible = true
redtip:set_pack(
"Appswitcher", self.tip, self.keytip.column, self.keytip.geometry,
self.keytip.exit and function() appswitcher:hide(true) end
)
end
-- Hide appswitcher widget
-----------------------------------------------------------------------------------------------------------------------
function appswitcher:hide(is_empty_call)
if not self.wibox then self:init() end
if not self.wibox.visible then return end
self.wibox.visible = false
redtip:remove_pack()
self.update_timer:stop()
awful.keygrabber.stop(self.keygrabber)
if not is_empty_call then focus_and_raise(self.clients_list[self.index]) end
end
-- Toggle appswitcher widget
-----------------------------------------------------------------------------------------------------------------------
function appswitcher:switch(args)
args = args or {}
if args.index then
if self.clients_list[args.index] then self.index = args.index end
else
local reverse = args.reverse or false
local diff = reverse and -1 or 1
self.index = iterate(self.clients_list, self.index, diff)
end
self.titlebox:set_markup(self.title_generator(self.clients_list[self.index]))
self.widget:emit_signal("widget::redraw_needed")
end
-- Set user hotkeys
-----------------------------------------------------------------------------------------------------------------------
function appswitcher:set_keys(keys, layout)
layout = layout or "all"
if keys then
self.keys[layout] = keys
if layout ~= "all" then self.keys.all = awful.util.table.join(self.keys.move, self.keys.action) end
end
self.tip = awful.util.table.join(self.keys.all, self._fake_keys)
end
-- End
-----------------------------------------------------------------------------------------------------------------------
return appswitcher

View File

@ -0,0 +1,221 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat titlebar helper widget --
-----------------------------------------------------------------------------------------------------------------------
-- Titlebar info widget
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local unpack = unpack or table.unpack
local beautiful = require("beautiful")
local awful = require("awful")
local wibox = require("wibox")
local redutil = require("redflat.util")
local redtitle = require("redflat.titlebar")
local redtip = require("redflat.float.hotkeys")
local svgbox = require("redflat.gauge.svgbox")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local bartip = {}
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
geometry = { width = 400, height = 60 },
border_width = 2,
font = "Sans 14",
set_position = nil,
names = {},
keytip = { geometry = { width = 600 } },
shape = nil,
margin = { icon = { title = { 10, 10, 2, 2 }, state = { 10, 10, 2, 2 } } },
icon = {
title = redutil.base.placeholder({ txt = "[]" }),
active = redutil.base.placeholder({ txt = "+" }),
absent = redutil.base.placeholder({ txt = "!" }),
disabled = redutil.base.placeholder({ txt = "X" }),
hidden = redutil.base.placeholder({ txt = "" }),
unknown = redutil.base.placeholder({ txt = "?" }),
},
color = { border = "#575757", text = "#aaaaaa", main = "#b1222b", wibox = "#202020",
gray = "#575757", icon = "#a0a0a0" },
}
return redutil.table.merge(style, redutil.table.check(beautiful, "float.bartip") or {})
end
-- key bindings
bartip.keys = {}
bartip.keys.bar = {
{
{ "Mod4" }, "b", function() redtitle.toggle(client.focus); bartip:update() end,
{ description = "Show/hide titlebar for focused client", group = "Titlebar control" }
},
{
{ "Mod4" }, "a", function() redtitle.toggle_all(); bartip:update() end,
{ description = "Show/hide titlebar for all clients", group = "Titlebar control" }
},
--{
-- { "Mod4" }, "v", function() redtitle.switch(client.focus); bartip:update() end,
-- { description = "Switch titlebar view for focused client", group = "Titlebar control" }
--},
{
{ "Mod4" }, "n", function() redtitle.global_switch(); bartip:update() end,
{ description = "Switch titlebar view for all clients", group = "Titlebar control" }
},
}
bartip.keys.action = {
{
{ "Mod4" }, "Super_L", function() bartip:hide() end,
{ description = "Close top list widget", group = "Action" }
},
{
{ "Mod4" }, "F1", function() redtip:show() end,
{ description = "Show hotkeys helper", group = "Action" }
},
}
bartip.keys.all = awful.util.table.join(bartip.keys.bar, bartip.keys.action)
-- Initialize widget
-----------------------------------------------------------------------------------------------------------------------
function bartip:init()
-- Initialize vars
--------------------------------------------------------------------------------
local style = default_style()
self.style = style
-- Create floating wibox for top widget
--------------------------------------------------------------------------------
self.wibox = wibox({
ontop = true,
bg = style.color.wibox,
border_width = style.border_width,
border_color = style.color.border,
shape = style.shape
})
self.wibox:geometry(style.geometry)
-- Widget layout setup
--------------------------------------------------------------------------------
self.label = wibox.widget.textbox()
self.label:set_align("center")
self.label:set_font(style.font)
local title_icon = svgbox(self.style.icon.title)
title_icon:set_color(self.style.color.icon)
self.state_icon = svgbox()
--self.wibox:set_widget(self.label)
self.wibox:setup({
wibox.container.margin(title_icon, unpack(self.style.margin.icon.title)),
self.label,
wibox.container.margin(self.state_icon, unpack(self.style.margin.icon.state)),
layout = wibox.layout.align.horizontal
})
-- Keygrabber
--------------------------------------------------------------------------------
self.keygrabber = function(mod, key, event)
if event == "release" then
for _, k in ipairs(self.keys.action) do
if redutil.key.match_grabber(k, mod, key) then k[3](); return end
end
else
for _, k in ipairs(self.keys.all) do
if redutil.key.match_grabber(k, mod, key) then k[3](); return end
end
end
end
-- First run actions
--------------------------------------------------------------------------------
self:set_keys()
end
-- Widget actions
-----------------------------------------------------------------------------------------------------------------------
local function get_title_state(c)
if not c then return "unknown" end
local model = redtitle.get_model(c)
local state = not model and "absent"
or model.hidden and "disabled"
or model.cutted and "hidden"
or "active"
return state, model and model.size or nil
end
-- Update
--------------------------------------------------------------------------------
function bartip:update()
local name = self.style.names[redtitle._index] or "Unknown"
local state, size = get_title_state(client.focus)
local size_mark = size and string.format(" [%d]", size) or ""
self.label:set_markup(string.format(
'<span color="%s">%s</span><span color="%s">%s</span>',
self.style.color.text, name, self.style.color.gray, size_mark
))
self.state_icon:set_image(self.style.icon[state])
self.state_icon:set_color(state == "absent" and self.style.color.main or self.style.color.icon)
end
-- Show
--------------------------------------------------------------------------------
function bartip:show()
if not self.wibox then self:init() end
if not self.wibox.visible then
if self.style.set_position then
self.style.set_position(self.wibox)
else
redutil.placement.centered(self.wibox, nil, mouse.screen.workarea)
end
redutil.placement.no_offscreen(self.wibox, self.style.screen_gap, screen[mouse.screen].workarea)
self:update()
self.wibox.visible = true
awful.keygrabber.run(self.keygrabber)
redtip:set_pack(
"Titlebar", self.tip, self.style.keytip.column, self.style.keytip.geometry,
function() self:hide() end
)
end
end
-- Hide
--------------------------------------------------------------------------------
function bartip:hide()
self.wibox.visible = false
awful.keygrabber.stop(self.keygrabber)
redtip:remove_pack()
end
-- Set user hotkeys
-----------------------------------------------------------------------------------------------------------------------
function bartip:set_keys(keys, layout)
layout = layout or "all"
if keys then
self.keys[layout] = keys
if layout ~= "all" then self.keys.all = awful.util.table.join(self.keys.bar, self.keys.action) end
end
self.tip = self.keys.all
end
-- End
-----------------------------------------------------------------------------------------------------------------------
return bartip

View File

@ -0,0 +1,62 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat brightness control widget --
-----------------------------------------------------------------------------------------------------------------------
-- Brightness control using xbacklight
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local string = string
local awful = require("awful")
local beautiful = require("beautiful")
local rednotify = require("redflat.float.notify")
local redutil = require("redflat.util")
-- Initialize tables and vars for module
-----------------------------------------------------------------------------------------------------------------------
local brightness = { dbus_correction = 1 }
local defaults = { down = false, step = 2 }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
notify = {},
}
return redutil.table.merge(style, redutil.table.check(beautiful, "float.brightness") or {})
end
-- Change brightness level
-----------------------------------------------------------------------------------------------------------------------
-- Change with xbacklight
------------------------------------------------------------
function brightness:change_with_xbacklight(args)
args = redutil.table.merge(defaults, args or {})
local command = string.format("xbacklight %s %d", args.down and "-dec" or "-inc", args.step)
awful.spawn.easy_async(command, self.info_with_xbacklight)
end
-- Update brightness level info
-----------------------------------------------------------------------------------------------------------------------
-- Update from xbacklight
------------------------------------------------------------
function brightness.info_with_xbacklight()
if not brightness.style then brightness.style = default_style() end
awful.spawn.easy_async(
"xbacklight -get",
function(output)
rednotify:show(redutil.table.merge(
{ value = output / 100, text = string.format('%.0f', output) .. "%" },
brightness.style.notify
))
end
)
end
-----------------------------------------------------------------------------------------------------------------------
return brightness

View File

@ -0,0 +1,383 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat client menu widget --
-----------------------------------------------------------------------------------------------------------------------
-- Custom float widget that provides client actions like the tasklist's window
-- menu but may be used outside of the tasklist context on any client. Useful
-- for titlebar click action or other custom client-related keybindings for
-- faster access of client actions without traveling to the tasklist.
-----------------------------------------------------------------------------------------------------------------------
-- Authored by M4he
-- Some code was taken from
------ redflat.widget.tasklist
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local ipairs = ipairs
local table = table
local beautiful = require("beautiful")
local awful = require("awful")
local wibox = require("wibox")
local redutil = require("redflat.util")
local separator = require("redflat.gauge.separator")
local redmenu = require("redflat.menu")
local svgbox = require("redflat.gauge.svgbox")
-- Initialize tables and vars for module
-----------------------------------------------------------------------------------------------------------------------
local clientmenu = { mt = {}, }
local last = {
client = nil,
screen = mouse.screen,
tag_screen = mouse.screen
}
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
icon = { unknown = redutil.base.placeholder(),
minimize = redutil.base.placeholder(),
close = redutil.base.placeholder() },
micon = { blank = redutil.base.placeholder({ txt = " " }),
check = redutil.base.placeholder({ txt = "+" }) },
layout_icon = { unknown = redutil.base.placeholder() },
actionline = { height = 28 },
stateline = { height = 35 },
state_iconsize = { width = 20, height = 20 },
action_iconsize = { width = 18, height = 18 },
separator = { marginh = { 3, 3, 5, 5 }, marginv = { 3, 3, 3, 3 } },
tagmenu = { icon_margin = { 2, 2, 2, 2 } },
hide_action = { move = true,
add = false,
min = true,
floating = false,
sticky = false,
ontop = false,
below = false,
maximized = false },
color = { main = "#b1222b", icon = "#a0a0a0", gray = "#404040", highlight = "#eeeeee" },
}
style.menu = {
ricon_margin = { 2, 2, 2, 2 },
hide_timeout = 1,
select_first = false,
nohide = true
}
return redutil.table.merge(style, redutil.table.check(beautiful, "float.clientmenu") or {})
end
-- Support functions
-----------------------------------------------------------------------------------------------------------------------
-- Function to build item list for submenu
--------------------------------------------------------------------------------
local function tagmenu_items(action, style)
local items = {}
for _, t in ipairs(last.screen.tags) do
if not awful.tag.getproperty(t, "hide") then
table.insert(
items,
{ t.name, function() action(t) end, style.micon.blank, style.micon.blank }
)
end
end
return items
end
-- Function to rebuild the submenu entries according to current screen's tags
--------------------------------------------------------------------------------
local function tagmenu_rebuild(menu, submenu_index, style)
for _, index in ipairs(submenu_index) do
local new_items
if index == 1 then
new_items = tagmenu_items(clientmenu.movemenu_action, style)
else
new_items = tagmenu_items(clientmenu.addmenu_action, style)
end
menu.items[index].child:replace_items(new_items)
end
end
-- Function to update tag submenu icons
--------------------------------------------------------------------------------
local function tagmenu_update(c, menu, submenu_index, style)
-- if the screen has changed (and thus the tags) since the last time the
-- tagmenu was built, rebuild it first
if last.tag_screen ~= mouse.screen then
tagmenu_rebuild(menu, submenu_index, style)
last.tag_screen = mouse.screen
end
for k, t in ipairs(last.screen.tags) do
if not awful.tag.getproperty(t, "hide") then
-- set layout icon for every tag
local l = awful.layout.getname(awful.tag.getproperty(t, "layout"))
local check_icon = style.micon.blank
if c then
local client_tags = c:tags()
check_icon = awful.util.table.hasitem(client_tags, t) and style.micon.check or check_icon
end
for _, index in ipairs(submenu_index) do
local submenu = menu.items[index].child
if submenu.items[k].icon then
submenu.items[k].icon:set_image(style.layout_icon[l] or style.layout_icon.unknown)
end
-- set "checked" icon if tag active for given client
-- otherwise set empty icon
if c then
if submenu.items[k].right_icon then
submenu.items[k].right_icon:set_image(check_icon)
end
end
-- update position of any visible submenu
if submenu.wibox.visible then submenu:show() end
end
end
end
end
-- Function to construct menu line with state icons
--------------------------------------------------------------------------------
local function state_line_construct(state_icons, setup_layout, style)
local stateboxes = {}
for i, v in ipairs(state_icons) do
-- create widget
stateboxes[i] = svgbox(v.icon)
stateboxes[i]:set_forced_width(style.state_iconsize.width)
stateboxes[i]:set_forced_height(style.state_iconsize.height)
-- set widget in line
local l = wibox.layout.align.horizontal()
l:set_expand("outside")
l:set_second(stateboxes[i])
setup_layout:add(l)
-- set mouse action
stateboxes[i]:buttons(awful.util.table.join(awful.button({}, 1,
function()
v.action()
stateboxes[i]:set_color(v.indicator(last.client) and style.color.main or style.color.gray)
end
)))
end
return stateboxes
end
-- Function to construct menu line with action icons (minimize, close)
--------------------------------------------------------------------------------
local function action_line_construct(setup_layout, style)
local sep = separator.vertical(style.separator)
local function actionbox_construct(icon, action)
local iconbox = svgbox(icon, nil, style.color.icon)
iconbox:set_forced_width(style.action_iconsize.width)
iconbox:set_forced_height(style.action_iconsize.height)
-- center iconbox both vertically and horizontally
local vert_wrapper = wibox.layout.align.vertical()
vert_wrapper:set_second(iconbox)
vert_wrapper:set_expand("outside")
local horiz_wrapper = wibox.layout.align.horizontal()
horiz_wrapper:set_second(vert_wrapper)
horiz_wrapper:set_expand("outside")
-- wrap into a background container to allow bg color change of area
local actionbox = wibox.container.background(horiz_wrapper)
-- set mouse action
actionbox:buttons(awful.util.table.join(awful.button({}, 1,
function()
action()
end
)))
actionbox:connect_signal("mouse::enter",
function()
iconbox:set_color(style.color.highlight)
actionbox.bg = style.color.main
end
)
actionbox:connect_signal("mouse::leave",
function()
iconbox:set_color(style.color.icon)
actionbox.bg = nil
end
)
return actionbox
end
-- minimize button
local minimize_box = actionbox_construct(
style.icon.minimize,
function()
last.client.minimized = not last.client.minimized
if style.hide_action["min"] then clientmenu.menu:hide() end
end
)
setup_layout:set_first(minimize_box)
-- separator
setup_layout:set_second(sep)
-- close button
local close_box = actionbox_construct(
style.icon.close,
function()
last.client:kill()
clientmenu.menu:hide()
end
)
setup_layout:set_third(close_box)
end
-- Calculate menu position
--------------------------------------------------------------------------------
local function coords_calc(menu)
local coords = mouse.coords()
coords.x = coords.x - menu.wibox.width / 2 - menu.wibox.border_width
return coords
end
-- Initialize window menu widget
-----------------------------------------------------------------------------------------------------------------------
function clientmenu:init(style)
style = redutil.table.merge(default_style(), style or {})
self.hide_check = function(action)
if style.hide_action[action] then self.menu:hide() end
end
-- Create array of state icons
-- associate every icon with action and state indicator
--------------------------------------------------------------------------------
local function icon_table_generator_prop(property)
return {
icon = style.icon[property] or style.icon.unknown,
action = function() last.client[property] = not last.client[property]; self.hide_check(property) end,
indicator = function(c) return c[property] end
}
end
local state_icons = {
icon_table_generator_prop("floating"),
icon_table_generator_prop("sticky"),
icon_table_generator_prop("ontop"),
icon_table_generator_prop("below"),
icon_table_generator_prop("maximized"),
}
-- Construct menu
--------------------------------------------------------------------------------
-- Window action line construction
------------------------------------------------------------
local actionline_horizontal = wibox.layout.align.horizontal()
actionline_horizontal:set_expand("outside")
local actionline = wibox.container.constraint(actionline_horizontal, "exact", nil, style.actionline.height)
action_line_construct(actionline_horizontal, style)
-- Window state line construction
------------------------------------------------------------
-- layouts
local stateline_horizontal = wibox.layout.flex.horizontal()
local stateline_vertical = wibox.layout.align.vertical()
stateline_vertical:set_second(stateline_horizontal)
stateline_vertical:set_expand("outside")
local stateline = wibox.container.constraint(stateline_vertical, "exact", nil, style.stateline.height)
-- set all state icons in line
local stateboxes = state_line_construct(state_icons, stateline_horizontal, style)
-- update function for state icons
local function stateboxes_update(c, icons, boxes)
for i, v in ipairs(icons) do
boxes[i]:set_color(v.indicator(c) and style.color.main or style.color.gray)
end
end
-- Separators config
------------------------------------------------------------
local menusep = { widget = separator.horizontal(style.separator) }
-- menu item actions
self.movemenu_action = function(t)
last.client:move_to_tag(t); awful.layout.arrange(t.screen); self.hide_check("move")
end
self.addmenu_action = function(t)
last.client:toggle_tag(t); awful.layout.arrange(t.screen); self.hide_check("add")
end
-- Construct tag submenus ("move" and "add")
------------------------------------------------------------
local movemenu_items = tagmenu_items(self.movemenu_action, style)
local addmenu_items = tagmenu_items(self.addmenu_action, style)
-- Create menu
------------------------------------------------------------
self.menu = redmenu({
theme = style.menu,
items = {
{ widget = actionline, focus = true },
menusep,
{ "Move to tag", { items = movemenu_items, theme = style.tagmenu } },
{ "Add to tag", { items = addmenu_items, theme = style.tagmenu } },
menusep,
{ widget = stateline, focus = true }
}
})
-- Widget update functions
--------------------------------------------------------------------------------
function self:update(c)
if self.menu.wibox.visible then
stateboxes_update(c, state_icons, stateboxes)
tagmenu_update(c, self.menu, { 1, 2 }, style)
end
end
-- Signals setup
--------------------------------------------------------------------------------
local client_signals = {
"property::ontop", "property::floating", "property::below", "property::maximized",
"tagged", "untagged" -- refresh tagmenu when client's tags change
}
for _, sg in ipairs(client_signals) do
client.connect_signal(sg, function() self:update(last.client) end)
end
end
-- Show window menu widget
-----------------------------------------------------------------------------------------------------------------------
function clientmenu:show(c)
-- init menu if needed
if not self.menu then self:init() end
-- toggle menu
if self.menu.wibox.visible and c == last.client and mouse.screen == last.screen then
self.menu:hide()
else
last.client = c
last.screen = mouse.screen
self.menu:show({ coords = coords_calc(self.menu) })
if self.menu.hidetimer.started then self.menu.hidetimer:stop() end
self:update(c)
end
end
return setmetatable(clientmenu, clientmenu.mt)

View File

@ -0,0 +1,358 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat floating window manager --
-----------------------------------------------------------------------------------------------------------------------
-- Widget to control single flating window size and posioning
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local unpack = unpack or table.unpack
local beautiful = require("beautiful")
local awful = require("awful")
local wibox = require("wibox")
local rednotify = require("redflat.float.notify")
local redutil = require("redflat.util")
local redtip = require("redflat.float.hotkeys")
local svgbox = require("redflat.gauge.svgbox")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local control = {}
-- Resize mode alias
local RESIZE_MODE = { FULL = 1, HORIZONTAL = 2, VERTICAL = 3 }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
geometry = { width = 400, height = 60 },
border_width = 2,
font = "Sans 14",
set_position = nil,
notify = {},
keytip = { geometry = { width = 600 } },
shape = nil,
steps = { 1, 10, 20, 50 },
default_step = 2,
onscreen = true,
margin = { icon = { onscreen = { 10, 10, 2, 2 }, mode = { 10, 10, 2, 2 } } },
icon = {
resize = {},
onscreen = redutil.base.placeholder({ txt = "X" }),
},
color = { border = "#575757", text = "#aaaaaa", main = "#b1222b", wibox = "#202020",
gray = "#575757", icon = "#a0a0a0" },
}
style.icon.resize[RESIZE_MODE.FULL] = redutil.base.placeholder({ txt = "F" })
style.icon.resize[RESIZE_MODE.HORIZONTAL] = redutil.base.placeholder({ txt = "H" })
style.icon.resize[RESIZE_MODE.VERTICAL] = redutil.base.placeholder({ txt = "V" })
return redutil.table.merge(style, redutil.table.check(beautiful, "float.control") or {})
end
-- key bindings
control.keys = {}
control.keys.control = {
{
{ "Mod4" }, "c", function() control:center() end,
{ description = "Put window at the center", group = "Window control" }
},
{
{ "Mod4" }, "q", function() control:resize() end,
{ description = "Increase window size", group = "Window control" }
},
{
{ "Mod4" }, "a", function() control:resize(true) end,
{ description = "Decrease window size", group = "Window control" }
},
{
{ "Mod4" }, "l", function() control:move("right") end,
{ description = "Move window to right", group = "Window control" }
},
{
{ "Mod4" }, "j", function() control:move("left") end,
{ description = "Move window to left", group = "Window control" }
},
{
{ "Mod4" }, "k", function() control:move("bottom") end,
{ description = "Move window to bottom", group = "Window control" }
},
{
{ "Mod4" }, "i", function() control:move("top") end,
{ description = "Move window to top", group = "Window control" }
},
{
{ "Mod4" }, "n", function() control:switch_resize_mode() end,
{ description = "Switch moving/resizing mode", group = "Mode" }
},
{
{ "Mod4" }, "s", function() control:switch_onscreen() end,
{ description = "Switch off screen check", group = "Mode" }
},
}
control.keys.action = {
{
{ "Mod4" }, "Super_L", function() control:hide() end,
{ description = "Close top list widget", group = "Action" }
},
{
{ "Mod4" }, "F1", function() redtip:show() end,
{ description = "Show hotkeys helper", group = "Action" }
},
}
control.keys.all = awful.util.table.join(control.keys.control, control.keys.action)
control._fake_keys = {
{
{}, "N", nil,
{ description = "Select move/resize step", group = "Mode",
keyset = { "1", "2", "3", "4", "5", "6", "7", "8", "9" } }
},
}
-- Support function
-----------------------------------------------------------------------------------------------------------------------
local function control_off_screen(window)
local wa = screen[mouse.screen].workarea
local newg = window:geometry()
if newg.width > wa.width then window:geometry({ width = wa.width, x = wa.x }) end
if newg.height > wa.height then window:geometry({ height = wa.height, y = wa.y }) end
redutil.placement.no_offscreen(window, nil, wa)
end
-- Initialize widget
-----------------------------------------------------------------------------------------------------------------------
function control:init()
-- Initialize vars
--------------------------------------------------------------------------------
local style = default_style()
self.style = style
self.client = nil
self.step = style.steps[style.default_step]
self.resize_mode = RESIZE_MODE.FULL
self.onscreen = style.onscreen
-- Create floating wibox for top widget
--------------------------------------------------------------------------------
self.wibox = wibox({
ontop = true,
bg = style.color.wibox,
border_width = style.border_width,
border_color = style.color.border,
shape = style.shape
})
self.wibox:geometry(style.geometry)
-- Widget layout setup
--------------------------------------------------------------------------------
self.label = wibox.widget.textbox()
self.label:set_align("center")
self.label:set_font(style.font)
self.onscreen_icon = svgbox(self.style.icon.onscreen)
self.onscreen_icon:set_color(self.onscreen and self.style.color.main or self.style.color.icon)
self.mode_icon = svgbox(self.style.icon.resize[self.resize_mode])
self.mode_icon:set_color(self.style.color.icon)
self.wibox:setup({
wibox.container.margin(self.onscreen_icon, unpack(self.style.margin.icon.onscreen)),
self.label,
wibox.container.margin(self.mode_icon, unpack(self.style.margin.icon.mode)),
layout = wibox.layout.align.horizontal
})
-- Keygrabber
--------------------------------------------------------------------------------
self.keygrabber = function(mod, key, event)
if event == "release" then
for _, k in ipairs(self.keys.action) do
if redutil.key.match_grabber(k, mod, key) then k[3](); return end
end
else
for _, k in ipairs(self.keys.all) do
if redutil.key.match_grabber(k, mod, key) then k[3](); return end
end
if string.match("123456789", key) then self:choose_step(tonumber(key)) end
end
end
-- First run actions
--------------------------------------------------------------------------------
self:set_keys()
end
-- Window control
-----------------------------------------------------------------------------------------------------------------------
-- Put window at center of screen
--------------------------------------------------------------------------------
function control:center()
if not self.client then return end
redutil.placement.centered(self.client, nil, mouse.screen.workarea)
if self.onscreen then control_off_screen(self.client) end
self:update()
end
-- Change window size
--------------------------------------------------------------------------------
function control:resize(is_shrinking)
if not self.client then return end
-- calculate new size
local g = self.client:geometry()
local d = self.step * (is_shrinking and -1 or 1)
local newg
if self.resize_mode == RESIZE_MODE.FULL then
newg = { x = g.x - d, y = g.y - d, width = g.width + 2 * d, height = g.height + 2 * d }
elseif self.resize_mode == RESIZE_MODE.HORIZONTAL then
newg = { x = g.x - d, width = g.width + 2 * d }
elseif self.resize_mode == RESIZE_MODE.VERTICAL then
newg = { y = g.y - d, height = g.height + 2 * d }
end
-- validate new size
if newg.height and newg.height <= 0 or newg.width and newg.width < 0 then return end
-- apply new size
self.client:geometry(newg)
if self.onscreen then control_off_screen(self.client) end
self:update()
end
-- Move by direction
--------------------------------------------------------------------------------
function control:move(direction)
if not self.client then return end
local g = self.client:geometry()
local d = self.step * ((direction == "left" or direction == "top") and -1 or 1)
if direction == "left" or direction == "right" then
self.client:geometry({ x = g.x + d })
else
self.client:geometry({ y = g.y + d })
end
if self.onscreen then control_off_screen(self.client) end
end
-- Widget actions
-----------------------------------------------------------------------------------------------------------------------
-- Update
--------------------------------------------------------------------------------
function control:update()
if not self.client then return end
local g = self.client:geometry()
local size_label = string.format("%sx%s", g.width, g.height)
self.label:set_markup(string.format(
'<span color="%s">%s</span><span color="%s"> [%d]</span>',
self.style.color.text, size_label, self.style.color.gray, self.step
))
end
-- Select move/resize step by index
--------------------------------------------------------------------------------
function control:choose_step(index)
if self.style.steps[index] then self.step = self.style.steps[index] end
self:update()
end
-- Switch resize mode
--------------------------------------------------------------------------------
function control:switch_resize_mode()
self.resize_mode = self.resize_mode + 1
if not awful.util.table.hasitem(RESIZE_MODE, self.resize_mode) then self.resize_mode = RESIZE_MODE.FULL end
self.mode_icon:set_image(self.style.icon.resize[self.resize_mode])
end
-- Switch onscreen mode
--------------------------------------------------------------------------------
function control:switch_onscreen()
self.onscreen = not self.onscreen
self.onscreen_icon:set_color(self.onscreen and self.style.color.main or self.style.color.icon)
if self.onscreen then
control_off_screen(self.client)
self:update()
end
end
-- Show
--------------------------------------------------------------------------------
function control:show()
if not self.wibox then self:init() end
if not self.wibox.visible then
-- check if focused client floating
local layout = awful.layout.get(mouse.screen)
local is_floating = client.focus and (client.focus.floating or layout.name == "floating")
and not client.focus.maximized
if not is_floating then
rednotify:show(redutil.table.merge({ text = "No floating window focused" }, self.style.notify))
return
end
self.client = client.focus
-- show widget
if self.style.set_position then
self.style.set_position(self.wibox)
else
redutil.placement.centered(self.wibox, nil, mouse.screen.workarea)
end
redutil.placement.no_offscreen(self.wibox, self.style.screen_gap, screen[mouse.screen].workarea)
self:update()
self.wibox.visible = true
awful.keygrabber.run(self.keygrabber)
redtip:set_pack(
"Floating window", self.tip, self.style.keytip.column, self.style.keytip.geometry,
function() self:hide() end
)
end
end
-- Hide
--------------------------------------------------------------------------------
function control:hide()
self.wibox.visible = false
awful.keygrabber.stop(self.keygrabber)
redtip:remove_pack()
self.client = nil
end
-- Set user hotkeys
-----------------------------------------------------------------------------------------------------------------------
function control:set_keys(keys, layout)
layout = layout or "all"
if keys then
self.keys[layout] = keys
if layout ~= "all" then self.keys.all = awful.util.table.join(self.keys.control, self.keys.action) end
end
self.tip = awful.util.table.join(self.keys.all, self._fake_keys)
end
-- End
-----------------------------------------------------------------------------------------------------------------------
return control

View File

@ -0,0 +1,88 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat decorations --
-----------------------------------------------------------------------------------------------------------------------
--
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local awful = require("awful")
local wibox = require("wibox")
local beautiful = require("beautiful")
local redutil = require("redflat.util")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local decor = {}
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function button_style()
local style = {
color = { shadow3 = "#1c1c1c", button = "#575757",
shadow4 = "#767676", text = "#cccccc", pressed = "404040" }
}
return redutil.table.merge(style, redutil.table.check(beautiful, "float.decoration.button") or {})
end
local function field_style()
local style = {
color = { bg = "#161616", shadow1 = "#141414", shadow2 = "#313131" }
}
return redutil.table.merge(style, redutil.table.check(beautiful, "float.decoration.field") or {})
end
-- Button element
-----------------------------------------------------------------------------------------------------------------------
function decor.button(textbox, action, style)
style = redutil.table.merge(button_style(), style or {})
-- Widget and layouts
--------------------------------------------------------------------------------
textbox:set_align("center")
local button_widget = wibox.container.background(textbox, style.color.button)
button_widget:set_fg(style.color.text)
local bord1 = wibox.container.background(wibox.container.margin(button_widget, 1, 1, 1, 1), style.color.shadow4)
local bord2 = wibox.container.background(wibox.container.margin(bord1, 1, 1, 1, 1), style.color.shadow3)
-- Button
--------------------------------------------------------------------------------
local press_func = function()
button_widget:set_bg(style.color.pressed)
end
local release_func = function()
button_widget:set_bg(style.color.button)
action()
end
button_widget:buttons(awful.button({}, 1, press_func, release_func))
-- Signals
--------------------------------------------------------------------------------
button_widget:connect_signal(
"mouse::leave",
function()
button_widget:set_bg(style.color.button)
end
)
--------------------------------------------------------------------------------
return bord2
end
-- Input text field
-----------------------------------------------------------------------------------------------------------------------
function decor.textfield(textbox, style)
style = redutil.table.merge(field_style(), style or {})
local field = wibox.container.background(textbox, style.color.bg)
local bord1 = wibox.container.background(wibox.container.margin(field, 1, 1, 1, 1), style.color.shadow1)
local bord2 = wibox.container.background(wibox.container.margin(bord1, 1, 1, 1, 1), style.color.shadow2)
return bord2
end
-- End
-----------------------------------------------------------------------------------------------------------------------
return decor

View File

@ -0,0 +1,361 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat hotkeys helper widget --
-----------------------------------------------------------------------------------------------------------------------
-- Widget with list of hotkeys
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local math = math
local unpack = unpack or table.unpack
local awful = require("awful")
local beautiful = require("beautiful")
local wibox = require("wibox")
local timer = require("gears.timer")
local redflat = require("redflat")
local redutil = require("redflat.util")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local hotkeys = { keypack = {}, lastkey = nil, cache = {}, boxes = {} }
local hasitem = awful.util.table.hasitem
-- key bindings
hotkeys.keys = { close = { "Escape" }, close_all = { "Super_L" } }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
geometry = { width = 800 },
border_margin = { 10, 10, 10, 10 },
tspace = 5,
delim = " ",
border_width = 2,
ltimeout = 0.05,
font = "Sans 12",
keyfont = "Sans bold 12",
titlefont = "Sans bold 14",
is_align = false,
separator = {},
heights = { key = 20, title = 24 },
color = { border = "#575757", text = "#aaaaaa", main = "#b1222b", wibox = "#202020",
gray = "#575757" },
shape = nil
}
return redutil.table.merge(style, redutil.table.check(beautiful, "float.hotkeys") or {})
end
-- Support functions
-----------------------------------------------------------------------------------------------------------------------
-- Calculate keytip length
--------------------------------------------------------------------------------
local function check_key_len(k)
local res = k.key:len()
for _, v in pairs(k.mod) do res = res + v:len() + 1 end
return res
end
-- Parse raw key table
--------------------------------------------------------------------------------
local keysort = function(a, b)
if a.length ~= b.length then
return a.length < b.length
else
return a.key < b.key
end
end
local function parse(rawkeys, columns)
local keys = {}
columns = columns or 1
local rk = {}
for _, k in ipairs(rawkeys) do if k[#k].description then table.insert(rk, k) end end
local p = math.ceil(#rk / columns)
-- dirty trick for raw sorting
local sp = {}
for _, v in ipairs(rk) do
if not hasitem(sp, v[#v].group) then table.insert(sp, v[#v].group) end
end
table.sort(rk, function(a, b)
local ai, bi = hasitem(sp, a[#a].group), hasitem(sp, b[#b].group)
if ai ~= bi then
return ai < bi
else
return a[2] < b[2]
end
end)
-- split keys to columns
for i = 1, columns do
keys[i] = { groups = {}, length = nil, names = {} }
local chunk = { unpack(rk, 1, p) }
rk = { unpack(rk, p + 1) }
-- build key column
for _, v in ipairs(chunk) do
local data = v[#v]
local k = {
mod = v[1], key = v[2],
description = data.description,
group = data.group,
keyset = data.keyset or { v[2] },
length = check_key_len({ mod = v[1], key = v[2] })
}
if not keys[i].groups[k.group] then
keys[i].groups[k.group] = {}
table.insert(keys[i].names, k.group) -- sorted names list to save group order
end
table.insert(keys[i].groups[k.group], k)
-- calculate max tip lenght
if not keys[i].length or keys[i].length < k.length then keys[i].length = k.length end
end
-- sort key by lenght inside group
for _, group in pairs(keys[i].groups) do table.sort(group, keysort) end
end
return keys
end
-- Form hotkeys helper text
--------------------------------------------------------------------------------
local function build_tip(pack, style, keypressed)
local text = {}
for i, column in ipairs(pack) do
local coltxt = {}
local height = 0
for _, name in pairs(column.names) do
local group = column.groups[name]
-- set group title
coltxt[#coltxt + 1] = string.format(
'<span font="%s" color="%s">%s</span>',
style.titlefont, style.color.gray, name
)
height = height + style.heights.title
-- build key tip line
for _, key in ipairs(group) do
-- key with align
local line = string.format('<b>%s</b>', key.key)
if style.is_align then
--noinspection StringConcatenationInLoops
line = line .. string.rep(" ", column.length - key.length)
end
-- key with mods
if #key.mod > 0 then
local fm = {}
for ki, v in ipairs(key.mod) do fm[ki] = string.format('<b>%s</b>', v) end
table.insert(fm, line)
line = table.concat(fm, string.format('<span color="%s">+</span>', style.color.gray))
end
-- key with description
local clr = keypressed and hasitem(key.keyset, keypressed) and style.color.main or style.color.text
line = string.format(
'<span color="%s"><span font="%s">%s</span>%s%s</span>',
clr, style.keyfont, line, style.delim, key.description
)
coltxt[#coltxt + 1] = line
height = height + style.heights.key
end
end
text[i] = { text = table.concat(coltxt, '\n'), height = height }
end
return text
end
-- Initialize widget
-----------------------------------------------------------------------------------------------------------------------
function hotkeys:init()
-- Initialize vars
--------------------------------------------------------------------------------
local style = default_style()
self.style = style
self.tip = {}
-- summary vertical size for all elements except tip textboxes
-- used to auto adjust widget height
self.vertical_pag = style.border_margin[3] + style.border_margin[4] + style.tspace + 2
if style.separator.marginh then
self.vertical_pag = self.vertical_pag + style.separator.marginh[3] + style.separator.marginh[4]
end
-- alias
local bm = style.border_margin
-- Create floating wibox for top widget
--------------------------------------------------------------------------------
self.wibox = wibox({
ontop = true,
bg = style.color.wibox,
border_width = style.border_width,
border_color = style.color.border,
shape = style.shape
})
self.wibox:geometry(style.geometry)
-- Widget layout setup
--------------------------------------------------------------------------------
self.layout = wibox.layout.flex.horizontal()
self.title = wibox.widget.textbox("Title")
self.title:set_align("center")
self.title:set_font(style.titlefont)
local _, th = self.title:get_preferred_size()
self.vertical_pag = self.vertical_pag + th
local subtitle = wibox.widget.textbox("Press any key to highlight tip, Escape for exit")
subtitle:set_align("center")
local _, sh = subtitle:get_preferred_size()
self.vertical_pag = self.vertical_pag + sh
self.wibox:setup({
{
{
self.title,
subtitle,
redflat.gauge.separator.horizontal(style.separator),
spacing = style.tspace,
layout = wibox.layout.fixed.vertical,
},
self.layout,
layout = wibox.layout.align.vertical,
},
left = bm[1], right = bm[2], top = bm[3], bottom = bm[4],
layout = wibox.container.margin,
})
-- Highlight timer
--------------------------------------------------------------------------------
local ltimer = timer({ timeout = style.ltimeout })
ltimer:connect_signal("timeout",
function()
ltimer:stop()
self:highlight()
end
)
-- Keygrabber
--------------------------------------------------------------------------------
self.keygrabber = function(_, key, event)
if event == "release" then
if hasitem(self.keys.close, key) then
self:hide(); return
end
if hasitem(self.keys.close_all, key) then
self:hide()
if self.keypack[#self.keypack].on_close then self.keypack[#self.keypack].on_close() end
return
end
end
self.lastkey = event == "press" and key or nil
ltimer:again()
end
end
-- Keypack managment
-----------------------------------------------------------------------------------------------------------------------
-- Set new keypack
--------------------------------------------------------------------------------
function hotkeys:set_pack(name, pack, columns, geometry, on_close)
if not self.wibox then self:init() end
if not self.cache[name] then self.cache[name] = parse(pack, columns) end
table.insert(
self.keypack,
{ name = name, pack = self.cache[name], geometry = geometry or self.style.geometry, on_close = on_close }
)
self.title:set_text(name .. " hotkeys")
self:highlight()
self:update_geometry(self.keypack[#self.keypack].geometry)
end
-- Remove current keypack
--------------------------------------------------------------------------------
function hotkeys:remove_pack()
table.remove(self.keypack)
self.title:set_text(self.keypack[#self.keypack].name .. " hotkeys")
self:highlight()
self:update_geometry(self.keypack[#self.keypack].geometry)
end
-- Calculate and set widget height
--------------------------------------------------------------------------------
function hotkeys:update_geometry(predefined)
local height = 0
for _, column in ipairs(self.tip) do height = math.max(height, column.height) end
self.wibox:geometry({ width = predefined.width, height = predefined.height or height + self.vertical_pag })
end
-- Highlight key tip
--------------------------------------------------------------------------------
function hotkeys:highlight()
self.tip = build_tip(self.keypack[#self.keypack].pack, self.style, self.lastkey)
self.layout:reset()
for i, column in ipairs(self.tip) do
if not self.boxes[i] then
self.boxes[i] = wibox.widget.textbox()
self.boxes[i]:set_valign("top")
self.boxes[i]:set_font(self.style.font)
end
self.boxes[i]:set_markup(column.text)
self.layout:add(self.boxes[i])
end
end
-- Show/hide widget
-----------------------------------------------------------------------------------------------------------------------
-- show
function hotkeys:show()
if not self.wibox then self:init() end
if not self.wibox.visible then
redutil.placement.centered(self.wibox, nil, mouse.screen.workarea)
self.wibox.visible = true
awful.keygrabber.run(self.keygrabber)
-- else
-- self:hide()
end
end
-- hide
function hotkeys:hide()
self.wibox.visible = false
self.lastkey = nil
self:highlight()
awful.keygrabber.stop(self.keygrabber)
end
-- End
-----------------------------------------------------------------------------------------------------------------------
return hotkeys

View File

@ -0,0 +1,10 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat library --
-----------------------------------------------------------------------------------------------------------------------
local wrequire = require("redflat.util").wrequire
local setmetatable = setmetatable
local lib = { _NAME = "redflat.float" }
return setmetatable(lib, { __index = wrequire })

View File

@ -0,0 +1,202 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat prefix hotkey manager --
-----------------------------------------------------------------------------------------------------------------------
-- Emacs like key key sequences
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local string = string
local table = table
local wibox = require("wibox")
local awful = require("awful")
local beautiful = require("beautiful")
local redflat = require("redflat")
local redutil = require("redflat.util")
local redtip = require("redflat.float.hotkeys")
-- Initialize tables and vars for module
-----------------------------------------------------------------------------------------------------------------------
local keychain = {}
local tip_cache = {}
local label_pattern = { Mod1 = "A", Mod4 = "M", Control = "C", Shift = "S" }
-- key bindings
keychain.service = { close = { "Escape" }, help = { "F1" }, stepback = { "BackSpace" } }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
geometry = { width = 220, height = 60 },
font = "Sans 14 bold",
border_width = 2,
keytip = { geometry = { width = 500 }, exit = false },
color = { border = "#575757", wibox = "#202020" },
shape = nil
}
return redflat.util.table.merge(style, redflat.util.table.check(beautiful, "float.keychain") or {})
end
-- Support functions
-----------------------------------------------------------------------------------------------------------------------
local function build_label(item)
if #item[1] == 0 then return item[2] end
local mods = {}
for _, m in ipairs(item[1]) do mods[#mods + 1] = label_pattern[m] end
return string.format("%s-%s", table.concat(mods, '-'), item[2])
end
local function build_tip(store, item, prefix)
prefix = prefix or build_label(item)
for _, k in ipairs(item[3]) do
local p = prefix .. " " .. build_label(k)
if type(k[3]) == "table" then
build_tip(store, k, p)
else
table.insert(store, { {}, p, nil, k[#k] })
end
end
return store
end
local function build_fake_keys(keys)
local res = {}
for _, keygroup in ipairs({
{ description = "Undo last key", group = "Action", keyname = "stepback" },
{ description = "Undo sequence", group = "Action", keyname = "close" },
{ description = "Show hotkeys helper", group = "Action", keyname = "help" },
})
do
for _, k in ipairs(keys[keygroup.keyname]) do
table.insert(res, {
{}, k, nil,
{ description = keygroup.description, group = keygroup.group }
})
end
end
return res
end
-- Main widget
-----------------------------------------------------------------------------------------------------------------------
-- Initialize keychain widget
--------------------------------------------------------------------------------
function keychain:init(style)
-- Init vars
------------------------------------------------------------
self.active = nil
self.parents = {}
self.sequence = ""
style = redflat.util.table.merge(default_style(), style or {})
self.style = style
-- Wibox
------------------------------------------------------------
self.wibox = wibox({
ontop = true,
bg = style.color.wibox,
border_width = style.border_width,
border_color = style.color.border,
shape = style.shape
})
self.wibox:geometry(style.geometry)
self.label = wibox.widget.textbox()
self.label:set_align("center")
self.wibox:set_widget(self.label)
self.label:set_font(style.font)
-- Keygrabber
------------------------------------------------------------
self.keygrabber = function(mod, key, event)
if event == "press" then return false end
-- dirty fix for first key release
if self.actkey == key and #mod == 0 then self.actkey = nil; return end
if awful.util.table.hasitem(self.service.close, key) then self:hide()
elseif awful.util.table.hasitem(self.service.stepback, key) then self:undo()
elseif awful.util.table.hasitem(self.service.help, key) then redtip:show()
else
for _, item in ipairs(self.active[3]) do
if redutil.key.match_grabber(item, mod, key) then self:activate(item); return end
end
end
end
end
-- Set current key item
--------------------------------------------------------------------------------
function keychain:activate(item, keytip)
if not self.wibox then self:init() end
self.actkey = keytip and item[2]
if type(item[3]) == "function" then
item[3]()
self:hide()
else
if not self.active then
redutil.placement.centered(self.wibox, nil, mouse.screen.workarea)
self.wibox.visible = true
awful.keygrabber.run(self.keygrabber)
else
self.parents[#self.parents + 1] = self.active
end
self.active = item
local label = build_label(self.active)
self.sequence = self.sequence == "" and label or self.sequence .. " " .. label
self.label:set_text(self.sequence)
end
-- build keys helper tip
if keytip then
if tip_cache[keytip] then
self.tip = tip_cache[keytip]
else
self.tip = awful.util.table.join(build_tip({}, item), build_fake_keys(self.service))
tip_cache[keytip] = self.tip
end
redtip:set_pack(keytip .. " keychain", self.tip, self.style.keytip.column, self.style.keytip.geometry)
end
end
-- Deactivate last key item
--------------------------------------------------------------------------------
function keychain:undo()
if #self.parents > 0 then
self.sequence = self.sequence:sub(1, - (#build_label(self.active) + 2))
self.label:set_text(self.sequence)
self.active = self.parents[#self.parents]
self.parents[#self.parents] = nil
else
self:hide()
end
end
-- Hide widget
--------------------------------------------------------------------------------
function keychain:hide()
self.wibox.visible = false
awful.keygrabber.stop(self.keygrabber)
self.active = nil
self.parents = {}
self.sequence = ""
redtip:remove_pack()
end
-- End
-----------------------------------------------------------------------------------------------------------------------
return keychain

View File

@ -0,0 +1,140 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat notify widget --
-----------------------------------------------------------------------------------------------------------------------
-- Floating widget with icon, text, and progress bar
-- special for volume and brightness indication
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local unpack = unpack or table.unpack
local beautiful = require("beautiful")
local wibox = require("wibox")
local timer = require("gears.timer")
local redutil = require("redflat.util")
local svgbox = require("redflat.gauge.svgbox")
local progressbar = require("redflat.gauge.graph.bar")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local notify = { last = {} }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
geometry = { width = 480, height = 100 },
screen_gap = 0,
set_position = nil,
border_margin = { 20, 20, 20, 20 },
elements_margin = { 20, 0, 10, 10 },
bar_width = 8,
font = "Sans 14",
border_width = 2,
timeout = 5,
icon = nil,
progressbar = {},
color = { border = "#575757", icon = "#aaaaaa", wibox = "#202020" },
shape = nil
}
return redutil.table.merge(style, redutil.table.check(beautiful, "float.notify") or {})
end
-- Initialize notify widget
-----------------------------------------------------------------------------------------------------------------------
function notify:init()
local style = default_style()
-- local icon = style.icon
self.style = style
-- Construct layouts
--------------------------------------------------------------------------------
local area = wibox.layout.align.horizontal()
local bar = progressbar(style.progressbar)
local image = svgbox()
local text = wibox.widget.textbox("100%")
text:set_align("center")
-- text:set_font(style.font)
local align_vertical = wibox.layout.align.vertical()
bar:set_forced_height(style.bar_width)
area:set_left(image)
area:set_middle(wibox.container.margin(align_vertical, unpack(style.elements_margin)))
-- Create floating wibox for notify widget
--------------------------------------------------------------------------------
self.wibox = wibox({
ontop = true,
bg = style.color.wibox,
border_width = style.border_width,
border_color = style.color.border,
shape = style.shape
})
self.wibox:set_widget(wibox.container.margin(area, unpack(style.border_margin)))
self.wibox:geometry(style.geometry)
-- Set info function
--------------------------------------------------------------------------------
function self:set(args)
args = args or {}
align_vertical:reset()
if args.value then
bar:set_value(args.value)
align_vertical:set_top(text)
align_vertical:set_bottom(bar)
else
align_vertical:set_middle(text)
end
if args.text then
text:set_text(args.text)
text:set_font(args.font ~= nil and args.font or style.font)
end
image:set_image(args.icon ~= nil and args.icon or style.icon)
image:set_color(args.color or style.color.icon)
end
-- Set autohide timer
--------------------------------------------------------------------------------
self.hidetimer = timer({ timeout = style.timeout })
self.hidetimer:connect_signal("timeout", function() self:hide() end)
-- Signals setup
--------------------------------------------------------------------------------
self.wibox:connect_signal("mouse::enter", function() self:hide() end)
end
-- Hide notify widget
-----------------------------------------------------------------------------------------------------------------------
function notify:hide()
self.wibox.visible = false
self.hidetimer:stop()
end
-- Show notify widget
-----------------------------------------------------------------------------------------------------------------------
function notify:show(args)
if not self.wibox then self:init() end
self:set(args)
if not self.wibox.visible or mouse.screen.index ~= self.last.screen then
if self.style.set_position then self.style.set_position(self.wibox) end
redutil.placement.no_offscreen(self.wibox, self.style.screen_gap, mouse.screen.workarea)
self.wibox.visible = true
end
self.last.screen = mouse.screen.index
self.hidetimer:again()
end
-- End
-----------------------------------------------------------------------------------------------------------------------
return notify

View File

@ -0,0 +1,506 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat audio player widget --
-----------------------------------------------------------------------------------------------------------------------
-- Music player widget
-- Display audio track information with mpris2
-- Using dbus-send to control
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local unpack = unpack or table.unpack
local math = math
local awful = require("awful")
local beautiful = require("beautiful")
local wibox = require("wibox")
local timer = require("gears.timer")
local redutil = require("redflat.util")
local progressbar = require("redflat.gauge.graph.bar")
local dashcontrol = require("redflat.gauge.graph.dash")
local svgbox = require("redflat.gauge.svgbox")
-- Initialize and vars for module
-----------------------------------------------------------------------------------------------------------------------
local player = { box = {}, listening = false }
local dbus_mpris = "dbus-send --print-reply=literal --session --dest=org.mpris.MediaPlayer2.%s "
.. "/org/mpris/MediaPlayer2 "
local dbus_get = dbus_mpris
.. "org.freedesktop.DBus.Properties.Get "
.. "string:'org.mpris.MediaPlayer2.Player' %s"
local dbus_getall = dbus_mpris
.. "org.freedesktop.DBus.Properties.GetAll "
.. "string:'org.mpris.MediaPlayer2.Player'"
local dbus_set = dbus_mpris
.. "org.freedesktop.DBus.Properties.Set "
.. "string:'org.mpris.MediaPlayer2.Player' %s"
local dbus_action = dbus_mpris
.. "org.mpris.MediaPlayer2.Player."
-- Helper function to decode URI string format
-----------------------------------------------------------------------------------------------------------------------
local function decodeURI(s)
return string.gsub(s, '%%(%x%x)', function(hex) return string.char(tonumber(hex, 16)) end)
end
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
geometry = { width = 520, height = 150 },
screen_gap = 0,
set_position = nil,
dashcontrol = {},
progressbar = {},
border_margin = { 20, 20, 20, 20 },
elements_margin = { 20, 0, 0, 0 },
volume_margin = { 0, 0, 0, 3 },
controls_margin = { 0, 0, 18, 8 },
buttons_margin = { 0, 0, 3, 3 },
pause_margin = { 12, 12, 0, 0 },
timeout = 5,
line_height = 26,
bar_width = 8, -- progress bar height
volume_width = 50,
titlefont = "Sans 12",
timefont = "Sans 12",
artistfont = "Sans 12",
border_width = 2,
icon = {
cover = redutil.base.placeholder(),
play = redutil.base.placeholder({ txt = "" }),
pause = redutil.base.placeholder({ txt = "[]" }),
next_tr = redutil.base.placeholder({ txt = "" }),
prev_tr = redutil.base.placeholder({ txt = "" }),
},
color = { border = "#575757", main = "#b1222b",
wibox = "#202020", gray = "#575757", icon = "#a0a0a0" },
shape = nil
}
return redutil.table.merge(style, redutil.table.check(beautiful, "float.player") or {})
end
-- Initialize player widget
-----------------------------------------------------------------------------------------------------------------------
function player:init(args)
-- Initialize vars
--------------------------------------------------------------------------------
args = args or {}
local _player = args.name or "vlc"
local style = default_style()
local show_album = false
self.info = { artist = "Unknown", album = "Unknown" }
self.style = style
self.last = { status = "Stopped", length = 5 * 60 * 1000000, volume = nil }
-- dbus vars
self.command = {
get_all = string.format(dbus_getall, _player),
get_position = string.format(dbus_get, _player, "string:'Position'"),
get_volume = string.format(dbus_get, _player, "string:'Volume'"),
set_volume = string.format(dbus_set, _player, "string:'Volume' variant:double:"),
action = string.format(dbus_action, _player),
set_position = string.format(dbus_action, _player) .. "SetPosition objpath:/not/used int64:",
}
self._actions = { "PlayPause", "Next", "Previous" }
-- Construct layouts
--------------------------------------------------------------------------------
-- progressbar and icon
self.bar = progressbar(style.progressbar)
self.box.image = svgbox(style.icon.cover)
self.box.image:set_color(style.color.gray)
-- Text lines
------------------------------------------------------------
self.box.title = wibox.widget.textbox("Title")
self.box.artist = wibox.widget.textbox("Artist")
self.box.title:set_font(style.titlefont)
self.box.title:set_valign("top")
self.box.artist:set_font(style.artistfont)
self.box.artist:set_valign("top")
local text_area = wibox.layout.fixed.vertical()
text_area:add(wibox.container.constraint(self.box.title, "exact", nil, style.line_height))
text_area:add(wibox.container.constraint(self.box.artist, "exact", nil, style.line_height))
-- Control line
------------------------------------------------------------
-- playback buttons
local player_buttons = wibox.layout.fixed.horizontal()
local prev_button = svgbox(style.icon.prev_tr, nil, style.color.icon)
player_buttons:add(prev_button)
self.play_button = svgbox(style.icon.play, nil, style.color.icon)
player_buttons:add(wibox.container.margin(self.play_button, unpack(style.pause_margin)))
local next_button = svgbox(style.icon.next_tr, nil, style.color.icon)
player_buttons:add(next_button)
-- time indicator
self.box.time = wibox.widget.textbox("0:00")
self.box.time:set_font(style.timefont)
-- volume
self.volume = dashcontrol(style.dashcontrol)
local volumespace = wibox.container.margin(self.volume, unpack(style.volume_margin))
local volume_area = wibox.container.constraint(volumespace, "exact", style.volume_width, nil)
-- full line
local buttons_align = wibox.layout.align.horizontal()
buttons_align:set_expand("outside")
buttons_align:set_middle(wibox.container.margin(player_buttons, unpack(style.buttons_margin)))
local control_align = wibox.layout.align.horizontal()
control_align:set_middle(buttons_align)
control_align:set_right(self.box.time)
control_align:set_left(volume_area)
-- Bring it all together
------------------------------------------------------------
local align_vertical = wibox.layout.align.vertical()
align_vertical:set_top(text_area)
align_vertical:set_middle(wibox.container.margin(control_align, unpack(style.controls_margin)))
align_vertical:set_bottom(wibox.container.constraint(self.bar, "exact", nil, style.bar_width))
local area = wibox.layout.fixed.horizontal()
area:add(self.box.image)
area:add(wibox.container.margin(align_vertical, unpack(style.elements_margin)))
-- Buttons
------------------------------------------------------------
-- playback controll
self.play_button:buttons(awful.util.table.join(awful.button({}, 1, function() self:action("PlayPause") end)))
next_button:buttons(awful.util.table.join(awful.button({}, 1, function() self:action("Next") end)))
prev_button:buttons(awful.util.table.join(awful.button({}, 1, function() self:action("Previous") end)))
-- volume
self.volume:buttons(awful.util.table.join(
awful.button({}, 4, function() self:change_volume( 0.05) end),
awful.button({}, 5, function() self:change_volume(-0.05) end)
))
-- position
self.bar:buttons(awful.util.table.join(
awful.button(
{}, 1, function()
local coords = {
bar = mouse.current_widget_geometry,
wibox = mouse.current_wibox:geometry(),
mouse = mouse.coords(),
}
local position = (coords.mouse.x - coords.wibox.x - coords.bar.x) / coords.bar.width
awful.spawn.with_shell(self.command.set_position .. math.floor(self.last.length * position))
end
)
))
-- switch between artist and album info on mouse click
self.box.artist:buttons(awful.util.table.join(
awful.button({}, 1,
function()
show_album = not show_album
self.update_artist()
end
)
))
-- Create floating wibox for player widget
--------------------------------------------------------------------------------
self.wibox = wibox({
ontop = true,
bg = style.color.wibox,
border_width = style.border_width,
border_color = style.color.border,
shape = style.shape
})
self.wibox:set_widget(wibox.container.margin(area, unpack(style.border_margin)))
self.wibox:geometry(style.geometry)
-- Update info functions
--------------------------------------------------------------------------------
-- Function to set play button state
------------------------------------------------------------
self.set_play_button = function(state)
self.play_button:set_image(style.icon[state])
end
-- Function to set info for artist/album line
------------------------------------------------------------
self.update_artist = function()
if show_album then
self.box.artist:set_markup('<span color="' .. style.color.gray .. '">From </span>' .. self.info.album)
else
self.box.artist:set_markup('<span color="' .. style.color.gray .. '">By </span>' .. self.info.artist)
end
end
-- Set defs
------------------------------------------------------------
self.clear_info = function(is_att)
self.box.image:set_image(style.icon.cover)
self.box.image:set_color(is_att and style.color.main or style.color.gray)
self.box.time:set_text("0:00")
self.bar:set_value(0)
-- self.box.title:set_text("Stopped")
self.info = { artist = "", album = "" }
self.update_artist()
self.last.volume = nil
end
-- Main update function
------------------------------------------------------------
function self:update()
if self.last.status ~= "Stopped" then
awful.spawn.easy_async(
self.command.get_position,
function(output, _, _, exit_code)
-- dirty trick to clean up if player closed
if exit_code ~= 0 then
self.clear_info(true)
self.last.status = "Stopped"
return
end
-- set progress bar
local position = string.match(output, "int64%s+(%d+)")
local progress = position / self.last.length
self.bar:set_value(progress)
-- set current time
local ps = math.floor(position / 10^6)
local ct = string.format("%d:%02d", math.floor(ps / 60), ps % 60)
self.box.time:set_text(ct)
end
)
end
end
-- Set update timer
--------------------------------------------------------------------------------
self.updatetimer = timer({ timeout = style.timeout })
self.updatetimer:connect_signal("timeout", function() self:update() end)
-- Run dbus servise
--------------------------------------------------------------------------------
if not self.listening then self:listen() end
self:initialize_info()
end
-- Initialize all properties via dbus call
-- should be called only once for initialization before the dbus signals trigger the updates
-----------------------------------------------------------------------------------------------------------------------
function player:initialize_info()
awful.spawn.easy_async(
self.command.get_all,
function(output, _, _, exit_code)
local data = { Metadata = {} }
local function parse_dbus_value(ident)
local regex = "(" .. ident .. ")%s+([a-z0-9]+)%s+(.-)%s-%)\n"
local _, _, value = output:match(regex)
if not value then return nil end
-- check for int64 type field
local int64_val = value:match("int64%s+(%d+)")
if int64_val then return tonumber(int64_val) end
-- check for double type field
local double_val = value:match("double%s+([%d.]+)")
if double_val then return tonumber(double_val) end
-- check for array type field as table, extract first entry only
local array_val = value:match("array%s%[%s+([^%],]+)")
if array_val then return { array_val } end
return value
end
if exit_code == 0 then
data.Metadata["xesam:title"] = parse_dbus_value("xesam:title")
data.Metadata["xesam:artist"] = parse_dbus_value("xesam:artist")
data.Metadata["xesam:album"] = parse_dbus_value("xesam:album")
data.Metadata["mpris:artUrl"] = parse_dbus_value("mpris:artUrl")
data.Metadata["mpris:length"] = parse_dbus_value("mpris:length")
data["Volume"] = parse_dbus_value("Volume")
data["Position"] = parse_dbus_value("Position")
data["PlaybackStatus"] = parse_dbus_value("PlaybackStatus")
self:update_from_metadata(data)
end
end
)
end
-- Player playback control
-----------------------------------------------------------------------------------------------------------------------
function player:action(args)
if not awful.util.table.hasitem(self._actions, args) then return end
if not self.wibox then self:init() end
awful.spawn.with_shell(self.command.action .. args)
self:update()
end
-- Player volume control
-----------------------------------------------------------------------------------------------------------------------
function player:change_volume(step)
local v = (self.last.volume or 0) + step
if v > 1 then v = 1
elseif v < 0 then v = 0 end
self.last.volume = v
awful.spawn.with_shell(self.command.set_volume .. v)
end
-- Hide player widget
-----------------------------------------------------------------------------------------------------------------------
function player:hide()
self.wibox.visible = false
if self.updatetimer.started then self.updatetimer:stop() end
end
-- Show player widget
-----------------------------------------------------------------------------------------------------------------------
function player:show(geometry)
if not self.wibox then self:init() end
if not self.wibox.visible then
self:update()
if geometry then
self.wibox:geometry(geometry)
elseif self.style.set_position then
self.style.set_position(self.wibox)
else
awful.placement.under_mouse(self.wibox)
end
redutil.placement.no_offscreen(self.wibox, self.style.screen_gap, screen[mouse.screen].workarea)
self.wibox.visible = true
if self.last.status == "Playing" then self.updatetimer:start() end
else
self:hide()
end
end
-- Update property values from received metadata
-----------------------------------------------------------------------------------------------------------------------
function player:update_from_metadata(data)
-- empty call
if not data then return end
-- set track info if playing
if data.Metadata then
-- set song title
self.box.title:set_text(data.Metadata["xesam:title"] or "Unknown")
-- set album or artist info
self.info.artist = data.Metadata["xesam:artist"] and data.Metadata["xesam:artist"][1] or "Unknown"
self.info.album = data.Metadata["xesam:album"] or "Unknown"
self.update_artist()
-- set cover art
local has_cover = false
if data.Metadata["mpris:artUrl"] then
local image = string.match(data.Metadata["mpris:artUrl"], "file://(.+)")
if image then
self.box.image:set_color(nil)
has_cover = self.box.image:set_image(decodeURI(image))
end
end
if not has_cover then
-- reset to generic icon if no cover available
self.box.image:set_color(self.style.color.gray)
self.box.image:set_image(self.style.icon.cover)
end
-- track length
if data.Metadata["mpris:length"] then self.last.length = data.Metadata["mpris:length"] end
end
if data.PlaybackStatus then
-- check player status and set suitable play/pause button image
local state = data.PlaybackStatus == "Playing" and "pause" or "play"
self.set_play_button(state)
self.last.status = data.PlaybackStatus
-- stop/start update timer
if data.PlaybackStatus == "Playing" then
if self.wibox.visible then self.updatetimer:start() end
else
if self.updatetimer.started then self.updatetimer:stop() end
self:update()
end
-- clear track info if stoppped
if data.PlaybackStatus == "Stopped" then
self.clear_info()
end
end
-- volume
if data.Volume then
self.volume:set_value(data.Volume)
self.last.volume = data.Volume
elseif not self.last.volume then
-- try to grab volume explicitly if not supplied
awful.spawn.easy_async(
self.command.get_volume,
function(output, _, _, exit_code)
if exit_code ~= 0 then
return
end
local volume = tonumber(string.match(output, "double%s+([%d.]+)"))
if volume then
self.volume:set_value(volume)
self.last.volume = volume
end
end
)
end
end
-- Dbus signal setup
-- update some info which avaliable from dbus signal
-----------------------------------------------------------------------------------------------------------------------
function player:listen()
dbus.request_name("session", "org.freedesktop.DBus.Properties")
dbus.add_match(
"session",
"path=/org/mpris/MediaPlayer2, interface='org.freedesktop.DBus.Properties', member='PropertiesChanged'"
)
dbus.connect_signal("org.freedesktop.DBus.Properties",
function (_, _, data)
self:update_from_metadata(data)
end
)
self.listening = true
end
-- End
-----------------------------------------------------------------------------------------------------------------------
return player

View File

@ -0,0 +1,99 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat promt widget --
-----------------------------------------------------------------------------------------------------------------------
-- Promt widget with his own wibox placed on center of screen
-----------------------------------------------------------------------------------------------------------------------
-- Some code was taken from
------ awful.widget.prompt v3.5.2
------ (c) 2009 Julien Danjou
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local type = type
local unpack = unpack or table.unpack
local awful = require("awful")
local beautiful = require("beautiful")
local wibox = require("wibox")
local naughty = require("naughty")
local redutil = require("redflat.util")
local decoration = require("redflat.float.decoration")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local floatprompt = {}
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
geometry = { width = 620, height = 120 },
margin = { 20, 20, 40, 40 },
border_width = 2,
naughty = {},
color = { border = "#575757", wibox = "#202020" },
shape = nil
}
return redutil.table.merge(style, redutil.table.check(beautiful, "float.prompt") or {})
end
-- Initialize prompt widget
-- @param prompt Prompt to use
-----------------------------------------------------------------------------------------------------------------------
function floatprompt:init(args)
args = args or {}
local style = default_style()
self.style = style
-- Create prompt widget
--------------------------------------------------------------------------------
self.widget = wibox.widget.textbox()
self.widget:set_ellipsize("start")
self.prompt = args.prompt or " Run: "
self.decorated_widget = decoration.textfield(self.widget, style.field)
-- Create floating wibox for promt widget
--------------------------------------------------------------------------------
self.wibox = wibox({
ontop = true,
bg = style.color.wibox,
border_width = style.border_width,
border_color = style.color.border,
shape = style.shape
})
self.wibox:set_widget(wibox.container.margin(self.decorated_widget, unpack(style.margin)))
self.wibox:geometry(style.geometry)
end
-- Run method for prompt widget
-- Wibox appears on call and hides after command entered
-----------------------------------------------------------------------------------------------------------------------
function floatprompt:run()
if not self.wibox then self:init() end
redutil.placement.centered(self.wibox, nil, mouse.screen.workarea)
self.wibox.visible = true
awful.prompt.run({
prompt = self.prompt,
textbox = self.widget,
exe_callback = function(input)
local result = awful.spawn(input)
if type(result) == "string" then
local notify_args = redutil.table.merge({ title = "Prompt", text = result }, self.style.naughty)
naughty.notify(notify_args)
end
end,
history_path = awful.util.getdir("cache") .. "/history",
history_max = 30,
completion_callback = awful.completion.shell,
done_callback = function () self.wibox.visible = false end,
})
end
-- End
-----------------------------------------------------------------------------------------------------------------------
return floatprompt

View File

@ -0,0 +1,510 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat quick laucnher widget --
-----------------------------------------------------------------------------------------------------------------------
-- Quick application launch or switch
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local table = table
local unpack = unpack or table.unpack
local string = string
local math = math
local io = io
local os = os
local wibox = require("wibox")
local awful = require("awful")
local beautiful = require("beautiful")
local color = require("gears.color")
local redflat = require("redflat")
local redutil = require("redflat.util")
local redtip = require("redflat.float.hotkeys")
-- Initialize tables and vars for module
-----------------------------------------------------------------------------------------------------------------------
local qlaunch = { history = {}, store = {}, keys = {} }
local sw = redflat.float.appswitcher
local TPI = math.pi * 2
local switcher_keys = {}
for i = 1, 9 do switcher_keys[tostring(i)] = { app = "", run = "" } end
-- key bindings
qlaunch.forcemod = { "Control" }
qlaunch.keys.action = {
{
{}, "Escape", function() qlaunch:hide(true) end,
{ description = "Close widget", group = "Action" }
},
{
{}, "Return", function() qlaunch:run_and_hide() end,
{ description = "Run or rise selected app", group = "Action" }
},
{
{}, "s", function() qlaunch:set_new_app(qlaunch.switcher.selected, client.focus) end,
{ description = "Bind focused app to selected key", group = "Action" }
},
{
{}, "d", function() qlaunch:set_new_app(qlaunch.switcher.selected) end,
{ description = "Clear selected key", group = "Action" }
},
{
{}, "r", function() qlaunch:load_config(true) end,
{ description = "Reload config from disk", group = "Action" }
},
{
{ "Mod4" }, "F1", function() redtip:show() end,
{ description = "Show hotkeys helper", group = "Action" }
},
}
qlaunch.keys.all = awful.util.table.join({}, qlaunch.keys.action)
qlaunch._fake_keys = {
{
{}, "N", nil,
{ description = "Select app (run or rise if selected already) by key", group = "Action",
keyset = { "1", "2", "3", "4", "5", "6", "7", "8", "9" } }
},
{
{}, "N", nil,
{ description = "Select app (launch if selected already) by key", group = "Action" }
},
}
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
df_icon = redutil.base.placeholder({ txt = "X" }),
no_icon = redutil.base.placeholder(),
parser = {},
recoloring = false,
notify = {},
geometry = { width = 1680, height = 180 },
border_margin = { 20, 20, 10, 10 },
appline = { iwidth = 160, im = { 10, 10, 5, 5 }, igap = { 0, 0, 10, 10 }, lheight = 30 },
state = { gap = 4, radius = 3, size = 10, height = 20, width = 20 },
configfile = os.getenv("HOME") .. "/.cache/awesome/applist",
label_font = "Sans 14",
border_width = 2,
keytip = { geometry = { width = 500 }, exit = false },
color = { border = "#575757", text = "#aaaaaa", main = "#b1222b", urgent = "#32882d",
wibox = "#202020", icon = "#a0a0a0", bg = "#161616", gray = "#575757" },
shape = nil
}
return redutil.table.merge(style, redutil.table.check(beautiful, "float.qlaunch") or {})
end
-- Support functions
-----------------------------------------------------------------------------------------------------------------------
-- Get list of clients with given class
------------------------------------------------------------
local function get_clients(app)
local clients = {}
for _, c in ipairs(client.get()) do
if c.class:lower() == app then table.insert(clients, c) end
end
return clients
end
-- Set focus on given client
------------------------------------------------------------
local function focus_and_raise(c)
if c.minimized then c.minimized = false end
if not c:isvisible() then awful.tag.viewmore(c:tags(), c.screen) end
client.focus = c
c:raise()
end
-- Build filter for clients with given class
------------------------------------------------------------
local function build_filter(app)
return function(c)
return c.class:lower() == app
end
end
-- Check if file exist
------------------------------------------------------------
local function is_file_exists(file)
local f = io.open(file, "r")
if f then f:close(); return true else return false end
end
-- Widget construction functions
-----------------------------------------------------------------------------------------------------------------------
-- Build application state indicator
--------------------------------------------------------------------------------
local function build_state_indicator(style)
-- Initialize vars
------------------------------------------------------------
local widg = wibox.widget.base.make_widget()
local dx = style.state.size + style.state.gap
local ds = style.state.size - style.state.radius
local r = style.state.radius
-- updating values
local data = {
state = {},
height = style.state.height or nil,
width = style.state.width or nil
}
-- User functions
------------------------------------------------------------
function widg:setup(clist)
data.state = {}
for _, c in ipairs(clist) do
table.insert(data.state, { focused = client.focus == c, urgent = c.urgent, minimized = c.minimized })
end
self:emit_signal("widget::redraw_needed")
end
-- Fit
------------------------------------------------------------
function widg:fit(_, width, height)
return data.width or width, data.height or height
end
-- Draw
------------------------------------------------------------
function widg:draw(_, cr, width, height)
local n = #data.state
local x0 = (width - n * style.state.size - (n - 1) * style.state.gap) / 2
local y0 = (height - style.state.size) / 2
for i = 1, n do
cr:set_source(color(
data.state[i].focused and style.color.main or
data.state[i].urgent and style.color.urgent or
data.state[i].minimized and style.color.gray or style.color.icon
))
-- draw rounded rectangle
cr:arc(x0 + (i -1) * dx + ds, y0 + r, r, -TPI / 4, 0)
cr:arc(x0 + (i -1) * dx + ds, y0 + ds, r, 0, TPI / 4)
cr:arc(x0 + (i -1) * dx + r, y0 + ds, r, TPI / 4, TPI / 2)
cr:arc(x0 + (i -1) * dx + r, y0 + r, r, TPI / 2, 3 * TPI / 4)
cr:fill()
end
end
------------------------------------------------------------
return widg
end
-- Build icon with label item
--------------------------------------------------------------------------------
local function build_item(key, style)
local widg = {}
-- Label
------------------------------------------------------------
local label = wibox.widget({
markup = string.format('<span color="%s">%s</span>', style.color.text, key),
align = "center",
font = style.label_font,
forced_height = style.appline.lheight,
widget = wibox.widget.textbox,
})
widg.background = wibox.container.background(label, style.color.bg)
-- Icon
------------------------------------------------------------
widg.svgbox = redflat.gauge.svgbox()
local icon_align = wibox.widget({
nil,
widg.svgbox,
forced_width = style.appline.iwidth,
expand = "outside",
layout = wibox.layout.align.horizontal,
})
-- State
------------------------------------------------------------
widg.state = build_state_indicator(style)
-- Layout setup
------------------------------------------------------------
widg.layout = wibox.layout.align.vertical()
widg.layout:set_top(widg.state)
widg.layout:set_middle(wibox.container.margin(icon_align, unpack(style.appline.igap)))
widg.layout:set_bottom(widg.background)
------------------------------------------------------------
return widg
end
-- Build widget with application list
--------------------------------------------------------------------------------
local function build_switcher(keys, style)
-- Init vars
------------------------------------------------------------
local widg = { items = {}, selected = nil }
local middle_layout = wibox.layout.fixed.horizontal()
-- Sorted keys
------------------------------------------------------------
local sk = {}
for k in pairs(keys) do table.insert(sk, k) end
table.sort(sk)
-- Build icon row
------------------------------------------------------------
for _, key in ipairs(sk) do
widg.items[key] = build_item(key, style)
middle_layout:add(wibox.container.margin(widg.items[key].layout, unpack(style.appline.im)))
end
widg.layout = wibox.widget({
nil,
wibox.container.margin(middle_layout, unpack(style.border_margin)),
expand = "outside",
layout = wibox.layout.align.horizontal,
})
-- Winget functions
------------------------------------------------------------
function widg:update(store, idb)
self.selected = nil
for key, data in pairs(store) do
local icon = data.app == "" and style.no_icon or idb[data.app] or style.df_icon
self.items[key].svgbox:set_image(icon)
if style.recoloring then self.items[key].svgbox:set_color(style.color.icon) end
end
self:set_state(store)
end
function widg:set_state(store)
for k, item in pairs(self.items) do
local clist = get_clients(store[k].app)
item.state:setup(clist)
end
end
function widg:reset()
for _, item in pairs(self.items) do item.background:set_bg(style.color.bg) end
self.selected = nil
end
function widg:check_key(key, mod)
if self.items[key] then
if self.selected then self.items[self.selected].background:set_bg(style.color.bg) end
self.items[key].background:set_bg(style.color.main)
if self.selected == key then
local modcheck = #mod == #qlaunch.forcemod
for _, v in ipairs(mod) do modcheck = modcheck and awful.util.table.hasitem(qlaunch.forcemod, v) end
qlaunch:run_and_hide(modcheck)
else
self.selected = key
end
end
end
------------------------------------------------------------
return widg
end
-- Main widget
-----------------------------------------------------------------------------------------------------------------------
-- Build widget
--------------------------------------------------------------------------------
function qlaunch:init(args, style)
-- Init vars
------------------------------------------------------------
args = args or {}
local keys = args.keys or switcher_keys
style = redutil.table.merge(default_style(), style or {})
self.style = style
self.default_switcher_keys = keys
self.icon_db = redflat.service.dfparser.icon_list(style.parser)
self:load_config()
-- Wibox
------------------------------------------------------------
self.wibox = wibox({
ontop = true,
bg = style.color.wibox,
border_width = style.border_width,
border_color = style.color.border,
shape = style.shape
})
self.wibox:geometry(style.geometry)
redutil.placement.centered(self.wibox, nil, screen[mouse.screen].workarea)
-- Switcher widget
------------------------------------------------------------
self.switcher = build_switcher(self.store, style)
self.switcher:update(self.store, self.icon_db)
self.wibox:set_widget(self.switcher.layout)
self:set_keys()
-- Keygrabber
------------------------------------------------------------
self.keygrabber = function(mod, key, event)
if event == "press" then return false end
for _, k in ipairs(self.keys.all) do
if redutil.key.match_grabber(k, mod, key) then k[3](); return end
end
self.switcher:check_key(key, mod)
end
-- Connect additional signals
------------------------------------------------------------
client.connect_signal("focus", function(c) self:set_last(c) end)
awesome.connect_signal("exit", function() self:save_config() end)
end
-- Widget show/hide
--------------------------------------------------------------------------------
function qlaunch:show()
if not self.wibox then self:init() end
self.switcher:set_state(self.store)
self.wibox.visible = true
awful.keygrabber.run(self.keygrabber)
redtip:set_pack(
"Quick launch", self.tip, self.style.keytip.column, self.style.keytip.geometry,
self.style.keytip.exit and function() self:hide() end
)
end
function qlaunch:hide()
self.wibox.visible = false
awful.keygrabber.stop(self.keygrabber)
self.switcher:reset()
redtip:remove_pack()
end
function qlaunch:run_and_hide(forced_run)
if self.switcher.selected then
self:run_or_raise(self.switcher.selected, forced_run)
end
self:hide()
end
-- Switch to app
--------------------------------------------------------------------------------
function qlaunch:run_or_raise(key, forced_run)
local app = self.store[key].app
if app == "" then return end
local clients = get_clients(app)
local cnum = #clients
if cnum == 0 or forced_run then
-- open new application
if self.store[key].run ~= "" then awful.spawn.with_shell(self.store[key].run) end
elseif cnum == 1 then
-- switch to sole app
focus_and_raise(clients[1])
else
if awful.util.table.hasitem(clients, client.focus) then
-- run selection widget if wanted app focused
sw:show({ filter = build_filter(app), noaction = true })
else
-- switch to last focused if availible or first in list otherwise
local last = awful.util.table.hasitem(clients, self.history[app])
if last then
focus_and_raise(self.history[app])
else
focus_and_raise(clients[1])
end
end
end
end
-- Bind new application to given hotkey
--------------------------------------------------------------------------------
function qlaunch:set_new_app(key, c)
if not key then return end
if c then
local run_command = redutil.read.output(string.format("tr '\\0' ' ' < /proc/%s/cmdline", c.pid))
self.store[key] = { app = c.class:lower(), run = run_command }
local note = redutil.table.merge({text = string.format("%s binded with '%s'", c.class, key)}, self.style.notify)
redflat.float.notify:show(note)
else
self.store[key] = { app = "", run = "" }
local note = redutil.table.merge({text = string.format("'%s' key unbinded", key)}, self.style.notify)
redflat.float.notify:show(note)
end
self.switcher:reset()
self.switcher:update(self.store, self.icon_db)
end
-- Save information about last focused client in widget store
--------------------------------------------------------------------------------
function qlaunch:set_last(c)
if not c.class then return end
for _, data in pairs(self.store) do
if c.class:lower() == data.app then
self.history[data.app] = c
break
end
end
end
-- Application list save/load
--------------------------------------------------------------------------------
function qlaunch:load_config(need_reset)
if is_file_exists(self.style.configfile) then
for line in io.lines(self.style.configfile) do
local key, app, run = string.match(line, "key=(.+);app=(.*);run=(.*);")
self.store[key] = { app = app, run = run }
end
else
self.store = self.default_switcher_keys
end
if need_reset then
self.switcher:reset()
self.switcher:update(self.store, self.icon_db)
end
end
function qlaunch:save_config()
local file = io.open(self.style.configfile, "w+")
for key, data in pairs(self.store) do
file:write(string.format("key=%s;app=%s;run=%s;\n", key, data.app, data.run))
end
file:close()
end
-- Set user hotkeys
-----------------------------------------------------------------------------------------------------------------------
function qlaunch:set_keys(keys, layout)
layout = layout or "all"
if keys then
self.keys[layout] = keys
if layout ~= "all" then self.keys.all = awful.util.table.join({}, self.keys.action) end
end
self._fake_keys[2][1] = self.forcemod
self.tip = awful.util.table.join(self.keys.all, self._fake_keys)
end
-- End
-----------------------------------------------------------------------------------------------------------------------
return qlaunch

View File

@ -0,0 +1,156 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat tooltip --
-----------------------------------------------------------------------------------------------------------------------
-- Slightly modded awful tooltip
-- padding added
-- Proper placement on every text update
-----------------------------------------------------------------------------------------------------------------------
-- Some code was taken from
------ awful.tooltip v3.5.2
------ (c) 2009 Sébastien Gross
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local ipairs = ipairs
local wibox = require("wibox")
local awful = require("awful")
local beautiful = require("beautiful")
local timer = require("gears.timer")
local redutil = require("redflat.util")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local tooltip = { mt = {} }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
padding = { vertical = 3, horizontal = 5 },
timeout = 1,
font = "Sans 12",
border_width = 2,
set_position = nil,
color = { border = "#404040", text = "#aaaaaa", wibox = "#202020" },
shape = nil
}
return redutil.table.merge(style, redutil.table.check(beautiful, "float.tooltip") or {})
end
-- Create a new tooltip
-----------------------------------------------------------------------------------------------------------------------
function tooltip.new(args, style)
-- Initialize vars
--------------------------------------------------------------------------------
args = args or {}
local objects = args.objects or {}
style = redutil.table.merge(default_style(), style or {})
-- Construct tooltip window with wibox and textbox
--------------------------------------------------------------------------------
local ttp = { wibox = wibox({ type = "tooltip" }), tip = nil }
local tb = wibox.widget.textbox()
tb:set_align("center")
ttp.widget = tb
ttp.wibox:set_widget(tb)
tb:set_font(style.font)
-- configure wibox properties
ttp.wibox.visible = false
ttp.wibox.ontop = true
ttp.wibox.border_width = style.border_width
ttp.wibox.border_color = style.color.border
ttp.wibox.shape = style.shape
ttp.wibox:set_bg(style.color.wibox)
ttp.wibox:set_fg(style.color.text)
-- Tooltip size configurator
--------------------------------------------------------------------------------
function ttp:set_geometry()
local wibox_sizes = self.wibox:geometry()
local w, h = self.widget:get_preferred_size()
local requsted_width = w + 2*style.padding.horizontal
local requsted_height = h + 2*style.padding.vertical
if wibox_sizes.width ~= requsted_width or wibox_sizes.height ~= requsted_height then
self.wibox:geometry({
width = requsted_width,
height = requsted_height
})
end
end
-- Set timer to make delay before tooltip show
--------------------------------------------------------------------------------
local show_timer = timer({ timeout = style.timeout })
show_timer:connect_signal("timeout",
function()
ttp:set_geometry()
if style.set_position then
style.set_position(ttp.wibox)
else
awful.placement.under_mouse(ttp.wibox)
end
awful.placement.no_offscreen(ttp.wibox)
ttp.wibox.visible = true
show_timer:stop()
end)
-- Tooltip metods
--------------------------------------------------------------------------------
function ttp.show()
if not show_timer.started then show_timer:start() end
end
function ttp.hide()
if show_timer.started then show_timer:stop() end
if ttp.wibox.visible then ttp.wibox.visible = false end
end
function ttp:set_text(text)
if self.tip ~= text then
self.widget:set_text(text)
self.tip = text
if self.wibox.visible then
self:set_geometry()
self.wibox.x = mouse.coords().x - self.wibox.width / 2
awful.placement.no_offscreen(self.wibox)
end
end
end
function ttp:add_to_object(object)
object:connect_signal("mouse::enter", self.show)
object:connect_signal("mouse::leave", self.hide)
end
function ttp:remove_from_object(object)
object:disconnect_signal("mouse::enter", self.show)
object:disconnect_signal("mouse::leave", self.hide)
end
-- Add tooltip to objects
--------------------------------------------------------------------------------
if objects then
for _, object in ipairs(objects) do
ttp:add_to_object(object)
end
end
--------------------------------------------------------------------------------
return ttp
end
-- Config metatable to call tooltip module as function
-----------------------------------------------------------------------------------------------------------------------
function tooltip.mt:__call(...)
return tooltip.new(...)
end
return setmetatable(tooltip, tooltip.mt)

View File

@ -0,0 +1,379 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat top widget --
-----------------------------------------------------------------------------------------------------------------------
-- Widget with list of top processes
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local unpack = unpack or table.unpack
local awful = require("awful")
local beautiful = require("beautiful")
local wibox = require("wibox")
local timer = require("gears.timer")
local redutil = require("redflat.util")
local system = require("redflat.system")
local decoration = require("redflat.float.decoration")
local redtip = require("redflat.float.hotkeys")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local top = { keys = {} }
-- key bindings
top.keys.management = {
{
{}, "c", function() top:set_sort("cpu") end,
{ description = "Sort by CPU usage", group = "Management" }
},
{
{}, "m", function() top:set_sort("mem") end,
{ description = "Sort by RAM usage", group = "Management" }
},
}
top.keys.action = {
{
{}, "k", function() top.kill_selected() end,
{ description = "Kill process", group = "Action" }
},
{
{}, "Escape", function() top:hide() end,
{ description = "Close top list widget", group = "Action" }
},
{
{ "Mod4" }, "F1", function() redtip:show() end,
{ description = "Show hotkeys helper", group = "Action" }
},
}
top.keys.all = awful.util.table.join(top.keys.management, top.keys.action)
top._fake_keys = {
{
{}, "N", nil,
{ description = "Select process by key", group = "Management",
keyset = { "1", "2", "3", "4", "5", "6", "7", "8", "9" } }
},
}
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
timeout = 2,
screen_gap = 0,
set_position = nil,
geometry = { width = 460, height = 380 },
border_margin = { 10, 10, 10, 10 },
labels_width = { num = 30, cpu = 70, mem = 120 },
title_height = 48,
list_side_gap = 8,
border_width = 2,
bottom_height = 80,
button_margin = { 140, 140, 22, 22 },
keytip = { geometry = { width = 400 } },
title_font = "Sans 14 bold",
unit = { { "KB", -1 }, { "MB", 1024 }, { "GB", 1024^2 } },
color = { border = "#575757", text = "#aaaaaa", highlight = "#eeeeee", main = "#b1222b",
bg = "#161616", bg_second = "#181818", wibox = "#202020" },
shape = nil
}
return redutil.table.merge(style, redutil.table.check(beautiful, "float.top") or {})
end
-- Support functions
-----------------------------------------------------------------------------------------------------------------------
-- Sort functions
--------------------------------------------------------------------------------
local function sort_by_cpu(a, b)
return a.pcpu > b.pcpu
end
local function sort_by_mem(a, b)
return a.mem > b.mem
end
-- Fuction to build list item
--------------------------------------------------------------------------------
local function construct_item(style)
local item = {}
item.label = {
number = wibox.widget.textbox(),
name = wibox.widget.textbox(),
cpu = wibox.widget.textbox(),
mem = wibox.widget.textbox()
}
item.label.cpu:set_align("right")
item.label.mem:set_align("right")
local bg = style.color.bg
-- Construct item layouts
------------------------------------------------------------
local mem_label_with_gap = wibox.container.margin(item.label.mem, 0, style.list_side_gap)
local num_label_with_gap = wibox.container.margin(item.label.number, style.list_side_gap)
local right = wibox.layout.fixed.horizontal()
right:add(wibox.container.constraint(item.label.cpu, "exact", style.labels_width.cpu, nil))
right:add(wibox.container.constraint(mem_label_with_gap, "exact", style.labels_width.mem, nil))
local middle = wibox.layout.align.horizontal()
middle:set_left(item.label.name)
local left = wibox.container.constraint(num_label_with_gap, "exact", style.labels_width.num, nil)
local item_horizontal = wibox.layout.align.horizontal()
item_horizontal:set_left(left)
item_horizontal:set_middle(middle)
item_horizontal:set_right(right)
item.layout = wibox.container.background(item_horizontal, bg)
-- item functions
------------------------------------------------------------
function item:set_bg(color)
bg = color
item.layout:set_bg(color)
end
function item:set_select()
item.layout:set_bg(style.color.main)
item.layout:set_fg(style.color.highlight)
end
function item:set_unselect()
item.layout:set_bg(bg)
item.layout:set_fg(style.color.text)
end
function item:set(args)
if args.number then item.label.number:set_text(args.number) end
if args.name then item.label.name:set_text(args.name) end
if args.cpu then item.label.cpu:set_text(args.cpu) end
if args.mem then item.label.mem:set_text(args.mem) end
if args.pid then item.pid = args.pid end
end
------------------------------------------------------------
return item
end
-- Fuction to build top list
--------------------------------------------------------------------------------
local function list_construct(n, style, select_function)
local list = {}
local list_layout = wibox.layout.flex.vertical()
list.layout = wibox.container.background(list_layout, style.color.bg)
list.items = {}
for i = 1, n do
list.items[i] = construct_item(style)
list.items[i]:set({ number = i})
list.items[i]:set_bg((i % 2) == 1 and style.color.bg or style.color.bg_second)
list_layout:add(list.items[i].layout)
list.items[i].layout:buttons(awful.util.table.join(
awful.button({ }, 1, function() select_function(i) end)
))
end
return list
end
-- Initialize top widget
-----------------------------------------------------------------------------------------------------------------------
function top:init()
-- Initialize vars
--------------------------------------------------------------------------------
local number_of_lines = 9 -- number of lines in process list
local selected = {}
local cpu_storage = { cpu_total = {}, cpu_active = {} }
local style = default_style()
local sort_function, title, toplist
self.style = style
-- Select process function
--------------------------------------------------------------------------------
local function select_item(i)
if selected.number and selected.number ~= i then toplist.items[selected.number]:set_unselect() end
toplist.items[i]:set_select()
selected.pid = toplist.items[i].pid
selected.number = i
end
-- Set sorting rule
--------------------------------------------------------------------------------
function self:set_sort(args)
if args == "cpu" then
sort_function = sort_by_cpu
title:set({ cpu = "↓CPU", mem = "Memory"})
elseif args == "mem" then
sort_function = sort_by_mem
title:set({ cpu = "CPU", mem = "↓Memory"})
end
end
-- Kill selected process
--------------------------------------------------------------------------------
function self.kill_selected()
if selected.number then awful.spawn.with_shell("kill " .. selected.pid) end
self:update_list()
end
-- Widget keygrabber
--------------------------------------------------------------------------------
self.keygrabber = function(mod, key, event)
if event ~= "press" then return end
for _, k in ipairs(self.keys.all) do
if redutil.key.match_grabber(k, mod, key) then k[3](); return end
end
if string.match("123456789", key) then select_item(tonumber(key)) end
end
-- Build title
--------------------------------------------------------------------------------
title = construct_item(style)
title:set_bg(style.color.wibox)
title:set({ number = "#", name = "Process Name", cpu = "↓ CPU", mem = "Memory"})
for _, txtbox in pairs(title.label) do
txtbox:set_font(style.title_font)
end
title.label.cpu:buttons(awful.util.table.join(
awful.button({ }, 1, function() self:set_sort("cpu") end)
))
title.label.mem:buttons(awful.util.table.join(
awful.button({ }, 1, function() self:set_sort("mem") end)
))
-- Build top list
--------------------------------------------------------------------------------
toplist = list_construct(number_of_lines, style, select_item)
-- Update function
--------------------------------------------------------------------------------
function self:update_list()
local proc = system.proc_info(cpu_storage)
table.sort(proc, sort_function)
if selected.number then
toplist.items[selected.number]:set_unselect()
selected.number = nil
end
for i = 1, number_of_lines do
toplist.items[i]:set({
name = proc[i].name,
cpu = string.format("%.1f", proc[i].pcpu * 100),
--mem = string.format("%.0f", proc[i].mem) .. " MB",
mem = redutil.text.dformat(proc[i].mem, style.unit, 2, " "),
pid = proc[i].pid
})
if selected.pid and selected.pid == proc[i].pid then
toplist.items[i]:set_select()
selected.number = i
end
end
end
-- Construct widget layouts
--------------------------------------------------------------------------------
local buttonbox = wibox.widget.textbox("Kill")
local button_widget = decoration.button(buttonbox, self.kill_selected)
local button_layout = wibox.container.margin(button_widget, unpack(style.button_margin))
local bottom_area = wibox.container.constraint(button_layout, "exact", nil, style.bottom_height)
local area = wibox.layout.align.vertical()
area:set_top(wibox.container.constraint(title.layout, "exact", nil, style.title_height))
area:set_middle(toplist.layout)
area:set_bottom(bottom_area)
local list_layout = wibox.container.margin(area, unpack(style.border_margin))
-- Create floating wibox for top widget
--------------------------------------------------------------------------------
self.wibox = wibox({
ontop = true,
bg = style.color.wibox,
border_width = style.border_width,
border_color = style.color.border,
shape = style.shape
})
self.wibox:set_widget(list_layout)
self.wibox:geometry(style.geometry)
-- Update timer
--------------------------------------------------------------------------------
self.update_timer = timer({timeout = style.timeout})
self.update_timer:connect_signal("timeout", function() self:update_list() end)
-- First run actions
--------------------------------------------------------------------------------
self:set_keys()
self:set_sort("cpu")
self:update_list()
end
-- Hide top widget
-----------------------------------------------------------------------------------------------------------------------
function top:hide()
self.wibox.visible = false
self.update_timer:stop()
awful.keygrabber.stop(self.keygrabber)
redtip:remove_pack()
end
-- Show top widget
-----------------------------------------------------------------------------------------------------------------------
function top:show(srt)
if not self.wibox then self:init() end
if self.wibox.visible then
top:hide()
else
if srt then self:set_sort(srt) end
self:update_list()
if self.style.set_position then
self.style.set_position(self.wibox)
else
awful.placement.under_mouse(self.wibox)
end
redutil.placement.no_offscreen(self.wibox, self.style.screen_gap, screen[mouse.screen].workarea)
self.wibox.visible = true
self.update_timer:start()
awful.keygrabber.run(self.keygrabber)
redtip:set_pack("Top process", self.tip, self.style.keytip.column, self.style.keytip.geometry)
end
end
-- Set user hotkeys
-----------------------------------------------------------------------------------------------------------------------
function top:set_keys(keys, layout)
layout = layout or "all"
if keys then
self.keys[layout] = keys
if layout ~= "all" then self.keys.all = awful.util.table.join(self.keys.management, self.keys.action) end
end
self.tip = awful.util.table.join(self.keys.all, self._fake_keys)
end
-- End
-----------------------------------------------------------------------------------------------------------------------
return top

View File

@ -0,0 +1,80 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat volume indicator widget --
-----------------------------------------------------------------------------------------------------------------------
-- Indicator with audio icon
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local unpack = unpack or table.unpack
local wibox = require("wibox")
local beautiful = require("beautiful")
local redutil = require("redflat.util")
local svgbox = require("redflat.gauge.svgbox")
local reddash = require("redflat.gauge.graph.dash")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local audio = { mt = {} }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
width = 100,
icon = redutil.base.placeholder(),
gauge = reddash.new,
dash = {},
dmargin = { 10, 0, 0, 0 },
color = { icon = "#a0a0a0", mute = "#404040" }
}
return redutil.table.merge(style, redutil.table.check(beautiful, "gauge.audio.blue") or {})
end
-- Create a new audio widget
-- @param style Table containing colors and geometry parameters for all elemets
-----------------------------------------------------------------------------------------------------------------------
function audio.new(style)
-- Initialize vars
--------------------------------------------------------------------------------
style = redutil.table.merge(default_style(), style or {})
-- Construct widget
--------------------------------------------------------------------------------
local icon = svgbox(style.icon)
local layout = wibox.layout.fixed.horizontal()
layout:add(icon)
local dash
if style.gauge then
dash = style.gauge(style.dash)
layout:add(wibox.container.margin(dash, unpack(style.dmargin)))
end
local widg = wibox.container.constraint(layout, "exact", style.width)
-- User functions
------------------------------------------------------------
function widg:set_value(x) if dash then dash:set_value(x) end end
function widg:set_mute(mute)
icon:set_color(mute and style.color.mute or style.color.icon)
end
--------------------------------------------------------------------------------
return widg
end
-- Config metatable to call audio module as function
-----------------------------------------------------------------------------------------------------------------------
function audio.mt:__call(...)
return audio.new(...)
end
return setmetatable(audio, audio.mt)

View File

@ -0,0 +1,10 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat library --
-----------------------------------------------------------------------------------------------------------------------
local wrequire = require("redflat.util").wrequire
local setmetatable = setmetatable
local lib = { _NAME = "redflat.gauge.audio" }
return setmetatable(lib, { __index = wrequire })

View File

@ -0,0 +1,93 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat volume indicator widget --
-----------------------------------------------------------------------------------------------------------------------
-- Indicator with audio icon
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local string = string
local math = math
local wibox = require("wibox")
local beautiful = require("beautiful")
local redutil = require("redflat.util")
local svgbox = require("redflat.gauge.svgbox")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local audio = { mt = {} }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
icon = { volume = redutil.base.placeholder(), mute = redutil.base.placeholder() },
step = 0.05,
color = { main = "#b1222b", icon = "#a0a0a0", mute = "#404040" }
}
return redutil.table.merge(style, redutil.table.check(beautiful, "gauge.audio.red") or {})
end
-- Support functions
-----------------------------------------------------------------------------------------------------------------------
local function pattern_string(height, value, c1, c2)
return string.format("linear:0,%s:0,0:0,%s:%s,%s:%s,%s:1,%s", height, c1, value, c1, value, c2, c2)
end
-- Create a new audio widget
-- @param style Table containing colors and geometry parameters for all elemets
-----------------------------------------------------------------------------------------------------------------------
function audio.new(style)
-- Initialize vars
--------------------------------------------------------------------------------
style = redutil.table.merge(default_style(), style or {})
-- Icon widgets
------------------------------------------------------------
local icon = {}
icon.ready = svgbox(style.icon.volume)
icon.ready:set_color(style.color.icon)
icon.mute = svgbox(style.icon.mute)
icon.mute:set_color(style.color.mute)
-- Create widget
--------------------------------------------------------------------------------
local widg = wibox.container.background(icon.ready)
widg._data = { level = 0 }
-- User functions
------------------------------------------------------------
function widg:set_value(x)
if x > 1 then x = 1 end
if self.widget._image then
local level = math.floor(x / style.step) * style.step
if level ~= self._data.level then
self._data.level = level
local h = self.widget._image.height
icon.ready:set_color(pattern_string(h, level, style.color.main, style.color.icon))
end
end
end
function widg:set_mute(mute)
widg:set_widget(mute and icon.mute or icon.ready)
end
--------------------------------------------------------------------------------
return widg
end
-- Config metatable to call audio module as function
-----------------------------------------------------------------------------------------------------------------------
function audio.mt:__call(...)
return audio.new(...)
end
return setmetatable(audio, audio.mt)

View File

@ -0,0 +1,80 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat progressbar widget --
-----------------------------------------------------------------------------------------------------------------------
-- Horizontal progresspar
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local wibox = require("wibox")
local beautiful = require("beautiful")
local color = require("gears.color")
local redutil = require("redflat.util")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local progressbar = { mt = {} }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
color = { main = "#b1222b", gray = "#404040" }
}
return redutil.table.merge(style, redutil.table.check(beautiful, "gauge.graph.bar") or {})
end
-- Create a new progressbar widget
-- @param style Table containing colors and geometry parameters for all elemets
-----------------------------------------------------------------------------------------------------------------------
function progressbar.new(style)
-- Initialize vars
--------------------------------------------------------------------------------
style = redutil.table.merge(default_style(), style or {})
-- Create custom widget
--------------------------------------------------------------------------------
local widg = wibox.widget.base.make_widget()
widg._data = { value = 0 }
-- User functions
------------------------------------------------------------
function widg:set_value(x)
local value = x < 1 and x or 1
if self._data.value ~= value then
self._data.value = value
self:emit_signal("widget::redraw_needed")
end
end
-- Fit
------------------------------------------------------------
function widg:fit(_, width, height)
return width, height
end
-- Draw
------------------------------------------------------------
function widg:draw(_, cr, width, height)
cr:set_source(color(style.color.gray))
cr:rectangle(0, 0, width, height)
cr:fill()
cr:set_source(color(style.color.main))
cr:rectangle(0, 0, self._data.value * width, height)
cr:fill()
end
--------------------------------------------------------------------------------
return widg
end
-- Config metatable to call progressbar module as function
-----------------------------------------------------------------------------------------------------------------------
function progressbar.mt:__call(...)
return progressbar.new(...)
end
return setmetatable(progressbar, progressbar.mt)

View File

@ -0,0 +1,88 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat dashcontrol widget --
-----------------------------------------------------------------------------------------------------------------------
-- Horizontal progresspar with stairs form
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local math = math
local wibox = require("wibox")
local beautiful = require("beautiful")
local color = require("gears.color")
local redutil = require("redflat.util")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local dashcontrol = { mt = {} }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
plain = false,
bar = { width = 4, num = 10 },
color = { main = "#b1222b", gray = "#404040" }
}
return redutil.table.merge(style, redutil.table.check(beautiful, "gauge.graph.dash") or {})
end
-- Create a new dashcontrol widget
-- @param style Table containing colors and geometry parameters for all elemets
-----------------------------------------------------------------------------------------------------------------------
function dashcontrol.new(style)
-- Initialize vars
--------------------------------------------------------------------------------
style = redutil.table.merge(default_style(), style or {})
-- Create custom widget
--------------------------------------------------------------------------------
local widg = wibox.widget.base.make_widget()
widg._data = { value = 0, cnum = 0 }
-- User functions
------------------------------------------------------------
function widg:set_value(x)
self._data.value = x < 1 and x or 1
local num = math.ceil(widg._data.value * style.bar.num)
if num ~= self._data.cnum then
self._data.cnum = num
self:emit_signal("widget::redraw_needed")
end
end
-- Fit
------------------------------------------------------------
function widg:fit(_, width, height)
return width, height
end
-- Draw
------------------------------------------------------------
function widg:draw(_, cr, width, height)
local wstep = (width - style.bar.width) / (style.bar.num - 1)
local hstep = height / style.bar.num
--self._data.cnum = math.ceil(widg._data.value * style.bar.num)
for i = 1, style.bar.num do
cr:set_source(color(i > self._data.cnum and style.color.gray or style.color.main))
cr:rectangle((i - 1) * wstep, height, style.bar.width, style.plain and -height or - i * hstep)
cr:fill()
end
end
--------------------------------------------------------------------------------
return widg
end
-- Config metatable to call dashcontrol module as function
-----------------------------------------------------------------------------------------------------------------------
function dashcontrol.mt:__call(...)
return dashcontrol.new(...)
end
return setmetatable(dashcontrol, dashcontrol.mt)

View File

@ -0,0 +1,109 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat dotcount widget --
-----------------------------------------------------------------------------------------------------------------------
-- Simple graphical counter
-- Displaying current value by dots number
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local math = math
local wibox = require("wibox")
local beautiful = require("beautiful")
local color = require("gears.color")
local redutil = require("redflat.util")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local counter = { mt = {} }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
column_num = { 2, 5 }, -- {min, max}
row_num = 3,
dot_size = 5,
dot_gap_h = 5,
color = { main = "#b1222b", gray = "#575757" }
}
return redutil.table.merge(style, redutil.table.check(beautiful, "gauge.graph.dots") or {})
end
-- Support functions
-----------------------------------------------------------------------------------------------------------------------
--local function round(x)
-- return math.floor(x + 0.5)
--end
-- Create a new counter widget
-- @param style Table containing colors and geometry parameters for all elemets
-----------------------------------------------------------------------------------------------------------------------
function counter.new(style)
-- Initialize vars
--------------------------------------------------------------------------------
style = redutil.table.merge(default_style(), style or {})
-- Create custom widget
--------------------------------------------------------------------------------
local widg = wibox.widget.base.make_widget()
widg._data = {
count_num = 0,
column_num = style.column_num[1]
}
-- User functions
------------------------------------------------------------
function widg:set_num(num)
if num ~= self._data.count_num then
self._data.count_num = num
self._data.column_num = math.min(
math.max(style.column_num[1], math.ceil(num / style.row_num)), style.column_num[2]
)
self:emit_signal("widget::redraw_needed")
end
end
-- Fit
------------------------------------------------------------
function widg:fit(_, _, height)
local width = (style.dot_size + style.dot_gap_h) * self._data.column_num - style.dot_gap_h
return width, height
end
-- Draw
------------------------------------------------------------
function widg:draw(_, cr, width, height)
-- local maxnum = style.row_num * data.column_num
local gap_v = (height - style.row_num * style.dot_size) / (style.row_num - 1)
cr:translate(0, height)
for i = 1, style.row_num do
for j = 1, self._data.column_num do
local cc = (j + (i - 1) * self._data.column_num) <= self._data.count_num
and style.color.main or style.color.gray
cr:set_source(color(cc))
cr:rectangle(0, 0, style.dot_size, - style.dot_size)
cr:fill()
cr:translate(style.dot_size + style.dot_gap_h, 0)
end
cr:translate(- (style.dot_gap_h + width), - (style.dot_size + gap_v))
end
end
--------------------------------------------------------------------------------
return widg
end
-- Config metatable to call dotcount module as function
-----------------------------------------------------------------------------------------------------------------------
function counter.mt:__call(...)
return counter.new(...)
end
return setmetatable(counter, counter.mt)

View File

@ -0,0 +1,10 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat library --
-----------------------------------------------------------------------------------------------------------------------
local wrequire = require("redflat.util").wrequire
local setmetatable = setmetatable
local lib = { _NAME = "redflat.gauge.graph" }
return setmetatable(lib, { __index = wrequire })

View File

@ -0,0 +1,96 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat indicator widget --
-----------------------------------------------------------------------------------------------------------------------
-- Double mage indicator
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local string = string
local math = math
local wibox = require("wibox")
local beautiful = require("beautiful")
local redutil = require("redflat.util")
local svgbox = require("redflat.gauge.svgbox")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local dubgicon = { mt = {} }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
icon1 = redutil.base.placeholder(),
icon2 = redutil.base.placeholder(),
igap = 8,
step = 0.05,
is_vertical = false,
color = { main = "#b1222b", icon = "#a0a0a0" }
}
return redutil.table.merge(style, redutil.table.check(beautiful, "gauge.icon.double") or {})
end
-- Support functions
-----------------------------------------------------------------------------------------------------------------------
local function pattern_string_v(height, value, c1, c2)
return string.format("linear:0,%s:0,0:0,%s:%s,%s:%s,%s:1,%s", height, c1, value, c1, value, c2, c2)
end
local function pattern_string_h(width, value, c1, c2)
return string.format("linear:0,0:%s,0:0,%s:%s,%s:%s,%s:1,%s", width, c1, value, c1, value, c2, c2)
end
-- Create a new dubgicon widget
-- @param style Table containing colors and geometry parameters for all elemets
-----------------------------------------------------------------------------------------------------------------------
function dubgicon.new(style)
-- Initialize vars
--------------------------------------------------------------------------------
style = redutil.table.merge(default_style(), style or {})
local pattern = style.is_vertical and pattern_string_v or pattern_string_h
-- Create widget
--------------------------------------------------------------------------------
local fixed = wibox.layout.fixed.horizontal()
local layout = wibox.container.constraint(fixed, "exact", style.width)
layout._icon1 = svgbox(style.icon1)
layout._icon2 = svgbox(style.icon2)
layout._data = { level = { 0, 0 }}
fixed:add(wibox.container.margin(layout._icon1, 0, style.igap, 0, 0))
fixed:add(layout._icon2)
-- User functions
------------------------------------------------------------
function layout:set_value(value)
local level = {
math.floor((value[1] < 1 and value[1] or 1) / style.step) * style.step,
math.floor((value[2] < 1 and value[2] or 1) / style.step) * style.step
}
for i, widg in ipairs({ self._icon1, self._icon2 }) do
if widg._image and level[i] ~= layout._data.level[i] then
layout._data.level[i] = level[i]
local d = style.is_vertical and widg._image.height or widg._image.width
widg:set_color(pattern(d, level[i], style.color.main, style.color.icon))
end
end
end
--------------------------------------------------------------------------------
return layout
end
-- Config metatable to call dubgicon module as function
-----------------------------------------------------------------------------------------------------------------------
function dubgicon.mt:__call(...)
return dubgicon.new(...)
end
return setmetatable(dubgicon, dubgicon.mt)

View File

@ -0,0 +1,10 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat library --
-----------------------------------------------------------------------------------------------------------------------
local wrequire = require("redflat.util").wrequire
local setmetatable = setmetatable
local lib = { _NAME = "redflat.gauge.icon" }
return setmetatable(lib, { __index = wrequire })

View File

@ -0,0 +1,95 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat indicator widget --
-----------------------------------------------------------------------------------------------------------------------
-- Image indicator
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local math = math
local string = string
local beautiful = require("beautiful")
local wibox = require("wibox")
local redutil = require("redflat.util")
local svgbox = require("redflat.gauge.svgbox")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local gicon = { mt = {} }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
icon = redutil.base.placeholder(),
step = 0.05,
is_vertical = false,
color = { main = "#b1222b", icon = "#a0a0a0", urgent = "#32882d" }
}
return redutil.table.merge(style, redutil.table.check(beautiful, "gauge.icon.single") or {})
end
-- Support functions
-----------------------------------------------------------------------------------------------------------------------
local function pattern_string_v(height, value, c1, c2)
return string.format("linear:0,%s:0,0:0,%s:%s,%s:%s,%s:1,%s", height, c1, value, c1, value, c2, c2)
end
local function pattern_string_h(width, value, c1, c2)
return string.format("linear:0,0:%s,0:0,%s:%s,%s:%s,%s:1,%s", width, c1, value, c1, value, c2, c2)
end
-- Create a new gicon widget
-- @param style Table containing colors and geometry parameters for all elemets
-----------------------------------------------------------------------------------------------------------------------
function gicon.new(style)
-- Initialize vars
--------------------------------------------------------------------------------
style = redutil.table.merge(default_style(), style or {})
local pattern = style.is_vertical and pattern_string_v or pattern_string_h
-- Create widget
--------------------------------------------------------------------------------
local widg = wibox.container.background(svgbox(style.icon))
widg._data = {
color = style.color.main,
level = 0,
}
-- User functions
------------------------------------------------------------
function widg:set_value(x)
if x > 1 then x = 1 end
if self.widget._image then
local level = math.floor(x / style.step) * style.step
if level ~= self._data.level then
self._data.level = level
local d = style.is_vertical and self.widget._image.height or self._image.width
self.widget:set_color(pattern(d, level, self._data.color, style.color.icon))
end
end
end
function widg:set_alert(alert)
-- not sure about redraw after alert set
self._data.color = alert and style.color.urgent or style.color.main
end
--------------------------------------------------------------------------------
return widg
end
-- Config metatable to call gicon module as function
-----------------------------------------------------------------------------------------------------------------------
function gicon.mt:__call(...)
return gicon.new(...)
end
return setmetatable(gicon, gicon.mt)

View File

@ -0,0 +1,10 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat library --
-----------------------------------------------------------------------------------------------------------------------
local wrequire = require("redflat.util").wrequire
local setmetatable = setmetatable
local lib = { _NAME = "redflat.gauge" }
return setmetatable(lib, { __index = wrequire })

View File

@ -0,0 +1,109 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat monitor widget --
-----------------------------------------------------------------------------------------------------------------------
-- Widget with circle indicator
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local math = math
local wibox = require("wibox")
local beautiful = require("beautiful")
local color = require("gears.color")
local redutil = require("redflat.util")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local cirmon = { mt = {} }
local TPI = math.pi * 2
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
width = nil,
line_width = 4,
radius = 14,
iradius = 6,
step = 0.02,
color = { main = "#b1222b", gray = "#575757", icon = "#a0a0a0" }
}
return redutil.table.merge(style, redutil.table.check(beautiful, "gauge.monitor.circle") or {})
end
-- Create a new monitor widget
-- @param style Table containing colors and geometry parameters for all elemets
-----------------------------------------------------------------------------------------------------------------------
function cirmon.new(style)
-- Initialize vars
--------------------------------------------------------------------------------
style = redutil.table.merge(default_style(), style or {})
local cs = -TPI / 4
-- Create custom widget
--------------------------------------------------------------------------------
local widg = wibox.widget.base.make_widget()
widg._data = { color = style.color.icon, level = 0, alert = false }
if style.width then widg:set_forced_width(style.width) end
-- User functions
------------------------------------------------------------
function widg:set_value(x)
local value = x < 1 and x or 1
local level = math.floor(value / style.step) * style.step
if level ~= self._data.level then
self._data.level = level
self:emit_signal("widget::redraw_needed")
end
end
function widg:set_alert(alert)
if alert ~= self._data.alert then
self._data.alert = alert
self._data.color = alert and style.color.main or style.color.icon
self:emit_signal("widget::redraw_needed")
end
end
-- Fit
------------------------------------------------------------
function widg:fit(_, width, height)
local size = math.min(width, height)
return size, size
end
-- Draw
------------------------------------------------------------
function widg:draw(_, cr, width, height)
-- center circle
cr:set_source(color(self._data.color))
cr:arc(width / 2, height / 2, style.iradius, 0, TPI)
cr:fill()
-- progress circle
cr:set_line_width(style.line_width)
local cd = { TPI, TPI * self._data.level }
for i = 1, 2 do
cr:set_source(color(i > 1 and style.color.main or style.color.gray))
cr:arc(width / 2, height / 2, style.radius, cs, cs + cd[i])
cr:stroke()
end
end
--------------------------------------------------------------------------------
return widg
end
-- Config metatable to call monitor module as function
-----------------------------------------------------------------------------------------------------------------------
function cirmon.mt:__call(...)
return cirmon.new(...)
end
return setmetatable(cirmon, cirmon.mt)

View File

@ -0,0 +1,98 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat monitor widget --
-----------------------------------------------------------------------------------------------------------------------
-- Widget with dash indicator
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local math = math
local wibox = require("wibox")
local beautiful = require("beautiful")
local color = require("gears.color")
local redutil = require("redflat.util")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local dashmon = { mt = {} }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
width = 40,
line = { num = 5, height = 4 },
color = { main = "#b1222b", urgent = "#00725b", gray = "#575757" }
}
return redutil.table.merge(style, redutil.table.check(beautiful, "gauge.monitor.dash") or {})
end
-- Create a new monitor widget
-- @param style Table containing colors and geometry parameters for all elemets
-----------------------------------------------------------------------------------------------------------------------
function dashmon.new(style)
-- Initialize vars
--------------------------------------------------------------------------------
style = redutil.table.merge(default_style(), style or {})
-- Create custom widget
--------------------------------------------------------------------------------
local widg = wibox.widget.base.make_widget()
widg._data = { color = style.color.main, level = 0, alert = false }
if style.width then widg:set_forced_width(style.width) end
-- User functions
------------------------------------------------------------
function widg:set_value(x)
local value = x < 1 and x or 1
local level = math.ceil(style.line.num * value)
if level ~= self._data.level then
self._data.level = level
self:emit_signal("widget::redraw_needed")
end
end
function widg:set_alert(alert)
if alert ~= self._data.alert then
self._data.alert = alert
self._data.color = alert and style.color.urgent or style.color.main
self:emit_signal("widget::redraw_needed")
end
end
-- Fit
------------------------------------------------------------
function widg:fit(_, width, height)
return width, height
end
-- Draw
------------------------------------------------------------
function widg:draw(_, cr, width, height)
local gap = (height - style.line.height * style.line.num) / (style.line.num - 1)
local dy = style.line.height + gap
for i = 1, style.line.num do
cr:set_source(color(i <= self._data.level and self._data.color or style.color.gray))
cr:rectangle(0, height - (i - 1) * dy, width, - style.line.height)
cr:fill()
end
end
--------------------------------------------------------------------------------
return widg
end
-- Config metatable to call monitor module as function
-----------------------------------------------------------------------------------------------------------------------
function dashmon.mt:__call(...)
return dashmon.new(...)
end
return setmetatable(dashmon, dashmon.mt)

View File

@ -0,0 +1,130 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat doublemonitor widget --
-----------------------------------------------------------------------------------------------------------------------
-- Widget with two progressbar and icon
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local math = math
local unpack = unpack or table.unpack
local wibox = require("wibox")
local beautiful = require("beautiful")
local color = require("gears.color")
local redutil = require("redflat.util")
local svgbox = require("redflat.gauge.svgbox")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local doublemonitor = { mt = {} }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
line = { width = 4, v_gap = 6, gap = 4, num = 5 },
icon = redutil.base.placeholder(),
dmargin = { 10, 0, 0, 0 },
width = 100,
color = { main = "#b1222b", gray = "#575757", icon = "#a0a0a0", urgent = "#32882d" }
}
return redutil.table.merge(style, redutil.table.check(beautiful, "gauge.monitor.double") or {})
end
-- Create progressbar widget
-----------------------------------------------------------------------------------------------------------------------
local function pbar(style)
-- Create custom widget
--------------------------------------------------------------------------------
local widg = wibox.widget.base.make_widget()
widg._data = { level = { 0, 0 }}
-- User functions
------------------------------------------------------------
function widg:set_value(value)
local level = {
math.ceil((value[1] < 1 and value[1] or 1) * style.line.num),
math.ceil((value[2] < 1 and value[2] or 1) * style.line.num),
}
if level[1] ~= self._data.level[1] or level[2] ~= self._data.level[2] then
self._data.level[1] = level[1]
self._data.level[2] = level[2]
self:emit_signal("widget::redraw_needed")
end
end
-- Fit
------------------------------------------------------------
function widg:fit(_, width, height)
return width, height
end
-- Draw
------------------------------------------------------------
function widg:draw(_, cr, width, height)
local wd = (width + style.line.gap) / style.line.num - style.line.gap
local dy = (height - (2 * style.line.width + style.line.v_gap)) / 2
for i = 1, 2 do
for k = 1, style.line.num do
cr:set_source(color(k <= self._data.level[i] and style.color.main or style.color.gray))
cr:rectangle(
(k - 1) * (wd + style.line.gap), dy + (i - 1) * (style.line.width + style.line.v_gap),
wd, style.line.width
)
cr:fill()
end
end
end
--------------------------------------------------------------------------------
return widg
end
-- Cunstruct a new doublemonitor widget
-- @param style Table containing colors and geometry parameters for all elemets
-----------------------------------------------------------------------------------------------------------------------
function doublemonitor.new(style)
-- Initialize vars
--------------------------------------------------------------------------------
style = redutil.table.merge(default_style(), style or {})
-- Construct layout
--------------------------------------------------------------------------------
local fixed = wibox.layout.fixed.horizontal()
fixed:set_forced_width(style.width)
local widg = pbar(style)
local icon = svgbox(style.icon)
icon:set_color(style.color.icon)
fixed:add(icon)
fixed:add(wibox.container.margin(widg, unpack(style.dmargin)))
-- User functions
--------------------------------------------------------------------------------
function fixed:set_value(value)
widg:set_value(value)
end
function fixed:set_alert(alert)
icon:set_color(alert and style.color.urgent or style.color.icon)
end
--------------------------------------------------------------------------------
return fixed
end
-- Config metatable to call doublemonitor module as function
-----------------------------------------------------------------------------------------------------------------------
function doublemonitor.mt:__call(...)
return doublemonitor.new(...)
end
return setmetatable(doublemonitor, doublemonitor.mt)

View File

@ -0,0 +1,10 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat library --
-----------------------------------------------------------------------------------------------------------------------
local wrequire = require("redflat.util").wrequire
local setmetatable = setmetatable
local lib = { _NAME = "redflat.gauge.monitor" }
return setmetatable(lib, { __index = wrequire })

View File

@ -0,0 +1,113 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat monitor widget --
-----------------------------------------------------------------------------------------------------------------------
-- Widget with label and progressbar
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local math = math
local wibox = require("wibox")
local beautiful = require("beautiful")
local color = require("gears.color")
local redutil = require("redflat.util")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local monitor = { mt = {} }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
line = { height = 4, y = 30 },
font = { font = "Sans", size = 16, face = 0, slant = 0 },
text_shift = 22,
label = "MON",
width = 100,
step = 0.05,
color = { main = "#b1222b", gray = "#575757", icon = "#a0a0a0" }
}
return redutil.table.merge(style, redutil.table.check(beautiful, "gauge.monitor.plain") or {})
end
-- Create a new monitor widget
-- @param style Table containing colors and geometry parameters for all elemets
-----------------------------------------------------------------------------------------------------------------------
function monitor.new(style)
-- Initialize vars
--------------------------------------------------------------------------------
style = redutil.table.merge(default_style(), style or {})
-- Create custom widget
--------------------------------------------------------------------------------
local widg = wibox.widget.base.make_widget()
widg._data = { color = style.color.icon, level = 0, alert = false, label = style.label }
if style.width then widg:set_forced_width(style.width) end
-- User functions
------------------------------------------------------------
function widg:set_value(x)
local value = x < 1 and x or 1
local level = math.floor(value / style.step) * style.step
if level ~= self._data.level then
self._data.level = level
self:emit_signal("widget::redraw_needed")
end
end
function widg:set_label(label)
if label ~= self._data.label then
self._data.label = label
self:emit_signal("widget::redraw_needed")
end
end
function widg:set_alert(alert)
if alert ~= self._data.alert then
self._data.alert = alert
self._data.color = alert and style.color.main or style.color.icon
self:emit_signal("widget::redraw_needed")
end
end
-- Fit
------------------------------------------------------------
function widg:fit(_, width, height)
return width, height
end
-- Draw
------------------------------------------------------------
function widg:draw(_, cr, width)
-- label
cr:set_source(color(self._data.color))
redutil.cairo.set_font(cr, style.font)
redutil.cairo.textcentre.horizontal(cr, { width/2, style.text_shift }, self._data.label)
-- progressbar
local wd = { width, width * self._data.level }
for i = 1, 2 do
cr:set_source(color(i > 1 and style.color.main or style.color.gray))
cr:rectangle(0, style.line.y, wd[i], style.line.height)
cr:fill()
end
end
--------------------------------------------------------------------------------
return widg
end
-- Config metatable to call monitor module as function
-----------------------------------------------------------------------------------------------------------------------
function monitor.mt:__call(...)
return monitor.new(...)
end
return setmetatable(monitor, monitor.mt)

View File

@ -0,0 +1,87 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat separatoe widget --
-----------------------------------------------------------------------------------------------------------------------
-- Simple graphical separator to decorate panel
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local wibox = require("wibox")
local beautiful = require("beautiful")
local color = require("gears.color")
local redutil = require("redflat.util")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local separator = {}
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
marginv = { 0, 0, 0, 0 },
marginh = { 0, 0, 0, 0 },
color = { shadow1 = "#141414", shadow2 = "#313131" }
}
return redutil.table.merge(style, redutil.table.check(beautiful, "gauge.separator") or {})
end
-- Create a new separator widget
-- Total size two pixels bigger than sum of margins for general direction
-----------------------------------------------------------------------------------------------------------------------
local function separator_base(horizontal, style)
-- Initialize vars
--------------------------------------------------------------------------------
style = redutil.table.merge(default_style(), style or {})
if not style.margin then
style.margin = horizontal and style.marginh or style.marginv
end
-- Create custom widget
--------------------------------------------------------------------------------
local widg = wibox.widget.base.make_widget()
-- Fit
------------------------------------------------------------
function widg:fit(_, width, height)
if horizontal then
return width, 2 + style.margin[3] + style.margin[4]
else
return 2 + style.margin[1] + style.margin[2], height
end
end
-- Draw
------------------------------------------------------------
function widg:draw(_, cr, width, height)
local w = width - style.margin[1] - style.margin[2]
local h = height - style.margin[3] - style.margin[4]
cr:translate(style.margin[1], style.margin[3])
cr:set_source(color(style.color.shadow1))
if horizontal then cr:rectangle(0, 0, w, 1) else cr:rectangle(0, 0, 1, h) end
cr:fill()
cr:set_source(color(style.color.shadow2))
if horizontal then cr:rectangle(0, 1, w, 1) else cr:rectangle(1, 0, 1, h) end
cr:fill()
end
--------------------------------------------------------------------------------
return widg
end
-- Horizontal and vertial variants
-----------------------------------------------------------------------------------------------------------------------
function separator.vertical(style)
return separator_base(false, style)
end
function separator.horizontal(style)
return separator_base(true, style)
end
-----------------------------------------------------------------------------------------------------------------------
return separator

View File

@ -0,0 +1,218 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat svg icon widget --
-----------------------------------------------------------------------------------------------------------------------
-- Imagebox widget modification
-- Use Gtk PixBuf API to resize svg image
-- Color setup added
-----------------------------------------------------------------------------------------------------------------------
-- Some code was taken from
------ wibox.widget.imagebox v3.5.2
------ (c) 2010 Uli Schlachter
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local string = string
local type = type
local pcall = pcall
local print = print
local math = math
-- local Gdk = require("lgi").Gdk
-- local pixbuf = require("lgi").GdkPixbuf
-- local cairo = require("lgi").cairo
local base = require("wibox.widget.base")
local surface = require("gears.surface")
local color = require("gears.color")
local pixbuf
local function load_pixbuf()
local _ = require("lgi").Gdk
pixbuf = require("lgi").GdkPixbuf
end
local is_pixbuf_loaded = pcall(load_pixbuf)
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local svgbox = { mt = {} }
-- weak table is useless here
-- TODO: implement mechanics to clear cache
local cache = setmetatable({}, { __mode = 'k' })
-- Support functions
-----------------------------------------------------------------------------------------------------------------------
-- Check if given argument is SVG file
local function is_svg(args)
return type(args) == "string" and string.match(args, "%.svg")
end
-- Check if need scale image
local function need_scale(widg, width, height)
return (widg._image.width ~= width or widg._image.height ~= height) and widg.resize_allowed
end
-- Cache functions
local function get_cache(file, width, height)
return cache[file .. "-" .. width .. "x" .. height]
end
local function set_cache(file, width, height, surf)
cache[file .. "-" .. width .. "x" .. height] = surf
end
-- Get cairo pattern
local function get_current_pattern(cr)
cr:push_group()
cr:paint()
return cr:pop_group()
end
-- Create Gdk PixBuf from SVG file with given sizes
local function pixbuf_from_svg(file, width, height)
local cached = get_cache(file, width, height)
if cached then
return cached
else
-- naughty.notify({ text = file })
local buf = pixbuf.Pixbuf.new_from_file_at_scale(file, width, height, true)
set_cache(file, width, height, buf)
return buf
end
end
-- Returns a new svgbox
-----------------------------------------------------------------------------------------------------------------------
function svgbox.new(image, resize_allowed, newcolor)
-- Create custom widget
--------------------------------------------------------------------------------
local widg = base.make_widget()
-- User functions
------------------------------------------------------------
function widg:set_image(image_name)
local loaded_image
if type(image_name) == "string" then
local success, result = pcall(surface.load, image_name)
if not success then
print("Error while reading '" .. image_name .. "': " .. result)
return false
end
self.image_name = image_name
loaded_image = result
else
loaded_image = surface.load(image_name)
end
if loaded_image and (loaded_image.height <= 0 or loaded_image.width <= 0) then return false end
self._image = loaded_image
self.is_svg = is_svg(image_name)
self:emit_signal("widget::redraw_needed")
return true
end
function widg:set_color(new_color)
if self.color ~= new_color then
self.color = new_color
self:emit_signal("widget::redraw_needed")
end
end
function widg:set_resize(allowed)
self.resize_allowed = allowed
self:emit_signal("widget::redraw_needed")
end
function widg:set_vector_resize(allowed)
self.vector_resize_allowed = allowed
self:emit_signal("widget::redraw_needed")
end
-- Fit
------------------------------------------------------------
function widg:fit(_, width, height)
local fw, fh = self:get_forced_width(), self:get_forced_height()
if fw or fh then
return fw or width, fh or height
else
if not self._image then return 0, 0 end
local w, h = self._image.width, self._image.height
if self.resize_allowed or w > width or h > height then
local aspect = math.min(width / w, height / h)
return w * aspect, h * aspect
end
return w, h
end
end
-- Draw
------------------------------------------------------------
function widg:draw(_, cr, width, height)
if width == 0 or height == 0 or not self._image then return end
local w, h = self._image.width, self._image.height
local aspect = math.min(width / w, height / h)
cr:save()
-- let's scale the image so that it fits into (width, height)
if need_scale(self, width, height) then
if self.is_svg and self.vector_resize_allowed and is_pixbuf_loaded then
-- for vector image
local pixbuf_ = pixbuf_from_svg(self.image_name, math.floor(w * aspect), math.floor(h * aspect))
cr:set_source_pixbuf(pixbuf_, 0, 0)
else
-- for raster image
cr:scale(aspect, aspect)
cr:set_source_surface(self._image, 0, 0)
cr:scale(1/aspect, 1/aspect) -- fix this !!!
end
else
cr:set_source_surface(self._image, 0, 0)
end
-- set icon color if need
if self.color then
local pattern = get_current_pattern(cr)
cr:scale(aspect, aspect) -- fix this !!!
cr:set_source(color(self.color))
cr:scale(1/aspect, 1/aspect) -- fix this !!!
cr:mask(pattern, 0, 0)
else
cr:paint()
end
cr:restore()
end
--------------------------------------------------------------------------------
if resize_allowed ~= nil then
widg.resize_allowed = resize_allowed
else
widg.resize_allowed = true
end
widg.color = newcolor
widg.vector_resize_allowed = true
if image then widg:set_image(image) end
return widg
end
-- Config metatable to call svgbox module as function
-----------------------------------------------------------------------------------------------------------------------
function svgbox.mt:__call(...)
return svgbox.new(...)
end
return setmetatable(svgbox, svgbox.mt)

View File

@ -0,0 +1,125 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat tag widget --
-----------------------------------------------------------------------------------------------------------------------
-- Custom widget to display tag info
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local math = math
local wibox = require("wibox")
local beautiful = require("beautiful")
local color = require("gears.color")
local redutil = require("redflat.util")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local bluetag = { mt = {} }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
width = 80,
font = { font = "Sans", size = 16, face = 0, slant = 0 },
text_shift = 32,
point = { height = 4, gap = 8, dx = 6, width = 40 },
show_min = false,
color = { main = "#b1222b", gray = "#575757", icon = "#a0a0a0", urgent = "#32882d" }
}
return redutil.table.merge(style, redutil.table.check(beautiful, "gauge.tag.blue") or {})
end
-- Create a new tag widget
-- @param style Table containing colors and geometry parameters for all elemets
-----------------------------------------------------------------------------------------------------------------------
function bluetag.new(style)
-- Initialize vars
--------------------------------------------------------------------------------
style = redutil.table.merge(default_style(), style or {})
-- updating values
local data = {
state = { text = "TEXT" },
width = style.width or nil
}
-- Create custom widget
--------------------------------------------------------------------------------
local widg = wibox.widget.base.make_widget()
-- User functions
------------------------------------------------------------
function widg:set_state(state)
data.state = state
self:emit_signal("widget::redraw_needed")
end
function widg:set_width(width)
data.width = width
self:emit_signal("widget::redraw_needed")
end
-- Fit
------------------------------------------------------------
function widg:fit(_, width, height)
if data.width then
return math.min(width, data.width), height
else
return width, height
end
end
-- Draw
------------------------------------------------------------
function widg:draw(_, cr, width)
local n = #data.state.list
-- text
cr:set_source(color(
data.state.active and style.color.main
or (n == 0 or data.state.minimized) and style.color.gray
or style.color.icon
))
redutil.cairo.set_font(cr, style.font)
redutil.cairo.textcentre.horizontal(cr, { width / 2, style.text_shift }, data.state.text)
-- occupied mark
local x = (width - style.point.width) / 2
if n > 0 then
local l = (style.point.width - (n - 1) * style.point.dx) / n
for i = 1, n do
local cl = data.state.list[i].focus and style.color.main or
data.state.list[i].urgent and style.color.urgent or
data.state.list[i].minimized and style.show_min and style.color.gray or
style.color.icon
cr:set_source(color(cl))
cr:rectangle(x + (i - 1) * (style.point.dx + l), style.point.gap, l, style.point.height)
cr:fill()
end
else
cr:set_source(color(style.color.gray))
cr:rectangle((width - style.point.width) / 2, style.point.gap, style.point.width, style.point.height)
cr:fill()
end
end
--------------------------------------------------------------------------------
return widg
end
-- Config metatable to call bluetag module as function
-----------------------------------------------------------------------------------------------------------------------
function bluetag.mt:__call(...)
return bluetag.new(...)
end
return setmetatable(bluetag, bluetag.mt)

View File

@ -0,0 +1,89 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat tag widget --
-----------------------------------------------------------------------------------------------------------------------
-- Custom widget to display tag info
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local unpack = unpack or table.unpack
local awful = require("awful")
local wibox = require("wibox")
local beautiful = require("beautiful")
local redutil = require("redflat.util")
local svgbox = require("redflat.gauge.svgbox")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local greentag = { mt = {} }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
width = 80,
margin = { 2, 2, 2, 2 },
icon = { unknown = redutil.base.placeholder() },
color = { main = "#b1222b", gray = "#575757", icon = "#a0a0a0", urgent = "#32882d" },
}
return redutil.table.merge(style, redutil.table.check(beautiful, "gauge.tag.green") or {})
end
-- Create a new tag widget
-- @param style Table containing colors and geometry parameters for all elemets
-----------------------------------------------------------------------------------------------------------------------
function greentag.new(style)
-- Initialize vars
--------------------------------------------------------------------------------
style = redutil.table.merge(default_style(), style or {})
-- updating values
local data = {
state = {},
width = style.width or nil
}
-- Create custom widget
--------------------------------------------------------------------------------
local widg = wibox.layout.align.horizontal()
widg._svgbox = svgbox()
widg:set_middle(wibox.container.margin(widg._svgbox, unpack(style.margin)))
widg:set_forced_width(data.width)
widg:set_expand("outside")
-- User functions
------------------------------------------------------------
function widg:set_state(state)
data.state = state
local icon = style.icon[awful.layout.getname(state.layout)] or style.icon.unknown
self._svgbox:set_image(icon)
self._svgbox:set_color(
data.state.active and style.color.main
or data.state.urgent and style.color.urgent
or data.state.occupied and style.color.icon
or style.color.gray
)
end
function widg:set_width(width)
data.width = width
self.set_forced_width(width)
end
--------------------------------------------------------------------------------
return widg
end
-- Config metatable to call greentag module as function
-----------------------------------------------------------------------------------------------------------------------
function greentag.mt:__call(...)
return greentag.new(...)
end
return setmetatable(greentag, greentag.mt)

View File

@ -0,0 +1,10 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat library --
-----------------------------------------------------------------------------------------------------------------------
local wrequire = require("redflat.util").wrequire
local setmetatable = setmetatable
local lib = { _NAME = "redflat.gauge.tag" }
return setmetatable(lib, { __index = wrequire })

View File

@ -0,0 +1,141 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat tag widget --
-----------------------------------------------------------------------------------------------------------------------
-- Custom widget to display tag info
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local math = math
local wibox = require("wibox")
local beautiful = require("beautiful")
local color = require("gears.color")
local redutil = require("redflat.util")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local orangetag = { mt = {} }
local TPI = math.pi * 2
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
width = 50,
line_width = 4,
radius = 14,
iradius = 6,
cgap = TPI / 20,
show_min = true,
min_sections = 1,
text = false,
font = { font = "Sans", size = 16, face = 0, slant = 0 },
color = { main = "#b1222b", gray = "#575757", icon = "#a0a0a0", urgent = "#32882d" }
}
return redutil.table.merge(style, redutil.table.check(beautiful, "gauge.tag.orange") or {})
end
-- Create a new tag widget
-- @param style Table containing colors and geometry parameters for all elemets
-----------------------------------------------------------------------------------------------------------------------
function orangetag.new(style)
-- Initialize vars
--------------------------------------------------------------------------------
style = redutil.table.merge(default_style(), style or {})
-- updating values
local data = {
state = { text = "TEXT" },
width = style.width or nil
}
-- Create custom widget
--------------------------------------------------------------------------------
local widg = wibox.widget.base.make_widget()
-- User functions
------------------------------------------------------------
function widg:set_state(state)
data.state = state
self:emit_signal("widget::redraw_needed")
end
function widg:set_width(width)
data.width = width
self:emit_signal("widget::redraw_needed")
end
-- Fit
------------------------------------------------------------
function widg:fit(_, width, height)
if data.width then
return math.min(width, data.width), height
else
return width, height
end
end
-- Draw
------------------------------------------------------------
function widg:draw(_, cr, width, height)
local sections = math.max(#data.state.list, style.min_sections)
local step = (TPI - sections * style.cgap) / sections
local cl
-- active mark
cl = data.state.active and style.color.main or (data.state.occupied and style.color.icon or style.color.gray)
cr:set_source(color(cl))
if style.text then
-- text
redutil.cairo.set_font(cr, style.font)
local ext = cr:text_extents(tostring(#data.state.list))
cr:move_to((width - ext.width - ext.x_bearing) / 2, (height + ext.height + ext.x_bearing) / 2)
cr:show_text(data.state.text)
cr:stroke()
else
-- round
cr:arc(width / 2, height / 2, style.iradius, 0, TPI)
cr:fill()
end
-- occupied mark
cr:set_line_width(style.line_width)
for i = 1, sections do
local cs = -TPI / 4 + (i - 1) * (step + style.cgap) + style.cgap / 2
if data.state.list[i] then
cl = data.state.list[i].focus and style.color.main or
data.state.list[i].urgent and style.color.urgent or
data.state.list[i].minimized and style.show_min and style.color.gray or style.color.icon
else
cl = style.color.gray
end
cr:set_source(color(cl))
if sections == 1 then
cr:arc(width / 2, height / 2, style.radius, 0, TPI)
else
cr:arc(width / 2, height / 2, style.radius, cs, cs + step)
end
cr:stroke()
end
end
--------------------------------------------------------------------------------
return widg
end
-- Config metatable to call orangetag module as function
-----------------------------------------------------------------------------------------------------------------------
function orangetag.mt:__call(...)
return orangetag.new(...)
end
return setmetatable(orangetag, orangetag.mt)

View File

@ -0,0 +1,191 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat tag widget --
-----------------------------------------------------------------------------------------------------------------------
-- Custom widget to display tag info
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local math = math
local wibox = require("wibox")
local beautiful = require("beautiful")
local color = require("gears.color")
local redutil = require("redflat.util")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local redtag = { mt = {} }
-- Support functions
-----------------------------------------------------------------------------------------------------------------------
local function fill_geometry(width, height, geometry)
local zero_geometry = { x = 0, y = 0, width = width, height = height }
return redutil.table.merge(zero_geometry, geometry)
end
-- Cairo drawing functions
--------------------------------------------------------------------------------
local cairo_draw = {}
-- Tag active mark (line)
------------------------------------------------------------
function cairo_draw.active(cr, width, height, geometry)
geometry = fill_geometry(width, height, geometry)
cr:rectangle(geometry.x, geometry.y, geometry.width, geometry.height)
cr:fill()
end
-- Tag focus mark (rhombus)
------------------------------------------------------------
function cairo_draw.focus(cr, width, height, geometry)
geometry = fill_geometry(width, height, geometry)
cr:move_to(geometry.x + geometry.width / 2, geometry.y)
cr:rel_line_to(geometry.width / 2, geometry.height / 2)
cr:rel_line_to(- geometry.width / 2, geometry.height / 2)
cr:rel_line_to(- geometry.width / 2, - geometry.height / 2)
cr:close_path()
cr:fill()
end
-- Tag occupied mark (label)
------------------------------------------------------------
function cairo_draw.occupied(cr, width, height, geometry)
geometry = fill_geometry(width, height, geometry)
cr:move_to(geometry.x, geometry.y)
cr:rel_line_to(0, geometry.height)
cr:rel_line_to(geometry.width / 2, - geometry.width / 2)
cr:rel_line_to(geometry.width / 2, geometry.width / 2)
cr:rel_line_to(0, - geometry.height)
cr:close_path()
cr:fill()
end
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
width = 80,
font = { font = "Sans", size = 16, face = 0, slant = 0 },
text_shift = 22,
counter = { size = 12, margin = 2, coord = { 40, 35 } },
show_counter = true,
color = { main = "#b1222b", gray = "#575757", icon = "#a0a0a0", urgent = "#32882d",
wibox = "#202020" }
}
-- functions for state marks
style.marks = cairo_draw
-- geometry for state marks
style.geometry = {
active = { height = 5, y = 45 },
focus = { x = 5, y = 10, width = 10, height = 15 },
occupied = { x = 65, y = 10, width = 10, height = 15 }
}
return redutil.table.merge(style, redutil.table.check(beautiful, "gauge.tag.red") or {})
end
-- Create a new tag widget
-- @param style Table containing colors and geometry parameters for all elemets
-----------------------------------------------------------------------------------------------------------------------
function redtag.new(style)
-- Initialize vars
--------------------------------------------------------------------------------
style = redutil.table.merge(default_style(), style or {})
-- updating values
local data = {
state = { text = "TEXT" },
width = style.width or nil
}
-- Create custom widget
--------------------------------------------------------------------------------
local widg = wibox.widget.base.make_widget()
-- User functions
------------------------------------------------------------
function widg:set_state(state)
data.state = state
self:emit_signal("widget::redraw_needed")
end
function widg:set_width(width)
data.width = width
self:emit_signal("widget::redraw_needed")
end
-- Fit
------------------------------------------------------------
function widg:fit(_, width, height)
if data.width then
return math.min(width, data.width), height
else
return width, height
end
end
-- Draw
------------------------------------------------------------
function widg:draw(_, cr, width, height)
-- text
cr:set_source(color(style.color.icon))
redutil.cairo.set_font(cr, style.font)
redutil.cairo.textcentre.horizontal(cr, { width/2, style.text_shift }, data.state.text)
-- active mark
cr:set_source(color(data.state.active and style.color.main or style.color.gray))
style.marks.active(cr, width, height, style.geometry.active)
-- occupied mark
if data.state.occupied then
cr:set_source(color(data.state.urgent and style.color.urgent or style.color.main))
style.marks.occupied(cr, width, height, style.geometry.occupied)
end
-- focus mark
if data.state.focus then
cr:set_source(color(style.color.main))
style.marks.focus(cr, width, height, style.geometry.focus)
end
-- counter
if style.show_counter and #data.state.list > 0 then
cr:set_font_size(style.counter.size)
local ext = cr:text_extents(tostring(#data.state.list))
cr:set_source(color(style.color.wibox))
cr:rectangle(
style.counter.coord[1] - ext.width / 2 - style.counter.margin,
style.counter.coord[2] - ext.height / 2 - style.counter.margin,
ext.width + 2 * style.counter.margin,
ext.height + 2 * style.counter.margin
)
cr:fill()
cr:set_source(color(style.color.icon))
redutil.cairo.textcentre.full(cr, style.counter.coord, tostring(#data.state.list))
end
end
--------------------------------------------------------------------------------
return widg
end
-- Config metatable to call redtag module as function
-----------------------------------------------------------------------------------------------------------------------
function redtag.mt:__call(...)
return redtag.new(...)
end
return setmetatable(redtag, redtag.mt)

View File

@ -0,0 +1,114 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat tag widget --
-----------------------------------------------------------------------------------------------------------------------
-- Custom widget to display tag info
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local math = math
local wibox = require("wibox")
local beautiful = require("beautiful")
local color = require("gears.color")
local redutil = require("redflat.util")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local rubytag = { mt = {} }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
width = 50,
base = { pad = 5, height = 12, thickness = 2 },
mark = { pad = 10, height = 4 },
color = { main = "#b1222b", gray = "#575757", icon = "#a0a0a0", urgent = "#32882d" }
}
return redutil.table.merge(style, redutil.table.check(beautiful, "gauge.tag.ruby") or {})
end
-- Create a new tag widget
-- @param style Table containing colors and geometry parameters for all elemets
-----------------------------------------------------------------------------------------------------------------------
function rubytag.new(style)
-- Initialize vars
--------------------------------------------------------------------------------
style = redutil.table.merge(default_style(), style or {})
-- updating values
local data = {
width = style.width or nil
}
-- Create custom widget
--------------------------------------------------------------------------------
local widg = wibox.widget.base.make_widget()
-- User functions
------------------------------------------------------------
function widg:set_state(state)
data.state = state
self:emit_signal("widget::redraw_needed")
end
function widg:set_width(width)
data.width = width
self:emit_signal("widget::redraw_needed")
end
-- Fit
------------------------------------------------------------
function widg:fit(_, width, height)
if data.width then
return math.min(width, data.width), height
else
return width, height
end
end
-- Draw
------------------------------------------------------------
function widg:draw(_, cr, width, height)
-- state
local cl = data.state.active and style.color.main or style.color.gray
cr:set_source(color(cl))
cr:rectangle(
style.base.pad, math.floor((height - style.base.height) / 2),
width - 2 * style.base.pad, style.base.height
)
cr:set_line_width(style.base.thickness)
cr:stroke()
-- focus
cl = data.state.focus and style.color.main
or data.state.urgent and style.color.urgent
or (data.state.occupied and style.color.icon or style.color.gray)
cr:set_source(color(cl))
cr:rectangle(
style.mark.pad, math.floor((height - style.mark.height) / 2),
width - 2 * style.mark.pad, style.mark.height
)
cr:fill()
end
--------------------------------------------------------------------------------
return widg
end
-- Config metatable to call rubytag module as function
-----------------------------------------------------------------------------------------------------------------------
function rubytag.mt:__call(...)
return rubytag.new(...)
end
return setmetatable(rubytag, rubytag.mt)

View File

@ -0,0 +1,116 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat task widget --
-----------------------------------------------------------------------------------------------------------------------
-- Widget includes colored icon
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local wibox = require("wibox")
local beautiful = require("beautiful")
local unpack = unpack or table.unpack
local redutil = require("redflat.util")
local svgbox = require("redflat.gauge.svgbox")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local greentask = { mt = {} }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
width = 40,
margin = { 0, 0, 0, 0 },
df_icon = redutil.base.placeholder(),
counter = { font = "Sans 10", mask = "%d" },
color = { main = "#b1222b", gray = "#575757", icon = "#a0a0a0", urgent = "#32882d", wibox = "#202020" },
}
return redutil.table.merge(style, redutil.table.check(beautiful, "gauge.task.green") or {})
end
-- Create a new greentask widget
-- @param style Table containing colors and geometry parameters for all elemets
-----------------------------------------------------------------------------------------------------------------------
function greentask.new(style)
-- Initialize vars
--------------------------------------------------------------------------------
style = redutil.table.merge(default_style(), style or {})
-- updating values
local data = {
state = {},
width = style.width or nil
}
-- Create custom widget
--------------------------------------------------------------------------------
local widg = wibox.layout.stack()
widg._svgbox = svgbox()
widg._textbox = wibox.widget{
align = 'center',
valign = 'bottom',
font = style.counter.font,
widget = wibox.widget.textbox
}
widg._icon_layout = wibox.widget({
nil, wibox.container.margin(widg._svgbox, unpack(style.margin)),
layout = wibox.layout.align.horizontal,
expand = "outside",
})
widg._text_layout = wibox.widget({
nil, nil, widg._textbox,
-- nil, nil, wibox.container.background(widg._textbox, style.color.wibox),
layout = wibox.layout.align.vertical,
})
widg:add(widg._icon_layout)
widg:add(widg._text_layout)
widg:set_forced_width(data.width)
-- User functions
------------------------------------------------------------
function widg:set_state(state)
data.state = redutil.table.merge(data.state, state)
-- icon
local icon = state.icon or style.df_icon
self._svgbox:set_image(icon)
self._svgbox:set_color(
data.state.focus and style.color.main
or data.state.urgent and style.color.urgent
or data.state.minimized and style.color.gray
or style.color.icon
)
-- counter
self._text_layout:set_visible(state.num > 1)
self._textbox:set_markup(
string.format('<span background="%s">' .. style.counter.mask .. '</span>', style.color.wibox, state.num)
)
end
function widg:set_width(width)
data.width = width
self.set_forced_width(width)
end
--------------------------------------------------------------------------------
return widg
end
-- Config metatable to call greentask module as function
-----------------------------------------------------------------------------------------------------------------------
function greentask.mt:__call(...)
return greentask.new(...)
end
return setmetatable(greentask, greentask.mt)

View File

@ -0,0 +1,10 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat library --
-----------------------------------------------------------------------------------------------------------------------
local wrequire = require("redflat.util").wrequire
local setmetatable = setmetatable
local lib = { _NAME = "redflat.gauge.task" }
return setmetatable(lib, { __index = wrequire })

View File

@ -0,0 +1,120 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat task widget --
-----------------------------------------------------------------------------------------------------------------------
-- Widget includes label and decorative line
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local math = math
local wibox = require("wibox")
local beautiful = require("beautiful")
local color = require("gears.color")
local redutil = require("redflat.util")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local redtask = { mt = {} }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
width = 40,
line = { height = 4, y = 30 },
font = { font = "Sans", size = 16, face = 0, slant = 0 },
text_shift = 22,
counter = { size = 12, margin = 2 },
color = { main = "#b1222b", gray = "#575757", icon = "#a0a0a0",
urgent = "#32882d", wibox = "#202020" }
}
return redutil.table.merge(style, redutil.table.check(beautiful, "gauge.task.red") or {})
end
-- Create a new redtask widget
-- @param style Table containing colors and geometry parameters for all elemets
-----------------------------------------------------------------------------------------------------------------------
function redtask.new(style)
-- Initialize vars
--------------------------------------------------------------------------------
style = redutil.table.merge(default_style(), style or {})
-- updating values
local data = {
state = { text = "TXT" }
}
-- Create custom widget
--------------------------------------------------------------------------------
local widg = wibox.widget.base.make_widget()
-- User functions
------------------------------------------------------------
function widg:set_state(state)
data.state = redutil.table.merge(data.state, state)
self:emit_signal("widget::redraw_needed")
end
function widg:set_width(width)
data.width = width
self:emit_signal("widget::redraw_needed")
end
-- Fit
------------------------------------------------------------
function widg:fit(_, width, height)
if data.width then
return math.min(width, data.width), height
else
return width, height
end
end
-- Draw
------------------------------------------------------------
function widg:draw(_, cr, width)
-- label
cr:set_source(color(data.state.minimized and style.color.gray or style.color.icon))
redutil.cairo.set_font(cr, style.font)
redutil.cairo.textcentre.horizontal(cr, { width / 2, style.text_shift }, data.state.text)
-- line
local line_color = data.state.focus and style.color.main
or data.state.urgent and style.color.urgent
or style.color.gray
cr:set_source(color(line_color))
cr:rectangle(0, style.line.y, width, style.line.height)
cr:fill()
-- counter
if data.state.num > 1 then
cr:set_font_size(style.counter.size)
local ext = cr:text_extents(tostring(data.state.num))
cr:set_source(color(style.color.wibox))
cr:rectangle(
(width - ext.width) / 2 - style.counter.margin, style.line.y,
ext.width + 2 * style.counter.margin, style.counter.size
)
cr:fill()
cr:set_source(color(style.color.icon))
local coord = { width / 2, style.line.y + style.counter.size / 2 }
redutil.cairo.textcentre.horizontal(cr, coord, tostring(data.state.num))
end
end
--------------------------------------------------------------------------------
return widg
end
-- Config metatable to call redtask module as function
-----------------------------------------------------------------------------------------------------------------------
function redtask.mt:__call(...)
return redtask.new(...)
end
return setmetatable(redtask, redtask.mt)

View File

@ -0,0 +1,137 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat tag widget --
-----------------------------------------------------------------------------------------------------------------------
-- Custom widget to display tag info
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local math = math
local wibox = require("wibox")
local beautiful = require("beautiful")
local color = require("gears.color")
local redutil = require("redflat.util")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local rubytask = { mt = {} }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
width = 80,
font = { font = "Sans", size = 16, face = 0, slant = 0 },
text_shift = 26,
point = { size = 4, space = 3, gap = 3 },
underline = { height = 20, thickness = 4, gap = 36, dh = 4 },
color = { main = "#b1222b", gray = "#575757", icon = "#a0a0a0", urgent = "#32882d" }
}
return redutil.table.merge(style, redutil.table.check(beautiful, "gauge.task.ruby") or {})
end
-- Create a new tag widget
-- @param style Table containing colors and geometry parameters for all elemets
-----------------------------------------------------------------------------------------------------------------------
function rubytask.new(style)
-- Initialize vars
--------------------------------------------------------------------------------
style = redutil.table.merge(default_style(), style or {})
-- updating values
local data = {
state = { text = "TEXT" },
width = style.width or nil
}
-- Create custom widget
--------------------------------------------------------------------------------
local widg = wibox.widget.base.make_widget()
-- User functions
------------------------------------------------------------
function widg:set_state(state)
data.state = state
self:emit_signal("widget::redraw_needed")
end
function widg:set_width(width)
data.width = width
self:emit_signal("widget::redraw_needed")
end
-- Fit
------------------------------------------------------------
function widg:fit(_, width, height)
if data.width then
return math.min(width, data.width), height
else
return width, height
end
end
-- Draw
------------------------------------------------------------
function widg:draw(_, cr, width)
local n = #data.state.list
-- text
cr:set_source(color(
data.state.active and style.color.main
or data.state.minimized and style.color.gray
or style.color.icon
))
redutil.cairo.set_font(cr, style.font)
redutil.cairo.textcentre.horizontal(cr, { width / 2, style.text_shift }, data.state.text)
-- underline
cr:set_source(color(
data.state.focus and style.color.main
or data.state.minimized and style.color.gray
or style.color.icon
))
cr:move_to(0, style.underline.gap)
cr:rel_line_to(width, 0)
cr:rel_line_to(0, -style.underline.height)
cr:rel_line_to(-style.underline.thickness, style.underline.dh)
cr:rel_line_to(0, style.underline.height - style.underline.dh - style.underline.thickness)
cr:rel_line_to(2 * style.underline.thickness - width, 0)
cr:rel_line_to(0, style.underline.thickness + style.underline.dh - style.underline.height)
cr:rel_line_to(-style.underline.thickness, - style.underline.dh)
cr:close_path(-style.underline.thickness, 0)
cr:fill()
-- clients counter
local x = math.floor((width - style.point.size * n - style.point.space * (n - 1)) / 2)
local l = style.point.size + style.point.space
for i = 1, n do
cr:set_source(color(
data.state.list[i].focus and style.color.main or
data.state.list[i].urgent and style.color.urgent or
data.state.list[i].minimized and style.color.gray or
style.color.icon
))
cr:rectangle(x + (i - 1) * l, style.point.gap, style.point.size, style.point.size)
cr:fill()
end
end
--------------------------------------------------------------------------------
return widg
end
-- Config metatable to call rubytask module as function
-----------------------------------------------------------------------------------------------------------------------
function rubytask.mt:__call(...)
return rubytask.new(...)
end
return setmetatable(rubytask, rubytask.mt)

View File

@ -0,0 +1,12 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat library --
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
return setmetatable(
{ _NAME = "redflat" },
{ __index = function(table, key)
local module = rawget(table, key)
return module or require(table._NAME .. '.' .. key)
end }
)

View File

@ -0,0 +1,371 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat layout shared functions --
-----------------------------------------------------------------------------------------------------------------------
-- Hotkeys for layout manipulation
-- Handlers adapted for work with redflat navigator widget
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local awful = require("awful")
local navigator = require("redflat.service.navigator")
local ipairs = ipairs
local alayout = awful.layout
local redutil = require("redflat.util")
local redtip = require("redflat.float.hotkeys")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local common = { handler = {}, last = {}, tips = {}, keys = {}, mouse = {} }
common.wfactstep = 0.05
-- default keys
common.keys.base = {
{
{ "Mod4" }, "c", function() common.action.kill() end,
{ description = "Kill application", group = "Action" }
},
{
{}, "Escape", function() common.action.exit() end,
{ description = "Exit navigation mode", group = "Action" }
},
{
{ "Mod4" }, "Escape", function() common.action.exit() end,
{} -- hidden key
},
{
{ "Mod4" }, "Super_L", function() common.action.exit() end,
{ description = "Exit navigation mode", group = "Action" }
},
{
{ "Mod4" }, "F1", function() redtip:show() end,
{ description = "Show hotkeys helper", group = "Action" }
},
}
common.keys.swap = {
{
{ "Mod4" }, "Up", function() awful.client.swap.bydirection("up") end,
{ description = "Move application up", group = "Movement" }
},
{
{ "Mod4" }, "Down", function() awful.client.swap.bydirection("down") end,
{ description = "Move application down", group = "Movement" }
},
{
{ "Mod4" }, "Left", function() awful.client.swap.bydirection("left") end,
{ description = "Move application left", group = "Movement" }
},
{
{ "Mod4" }, "Right", function() awful.client.swap.bydirection("right") end,
{ description = "Move application right", group = "Movement" }
},
}
common.keys.tile = {
{
{ "Mod4" }, "l", function () awful.tag.incmwfact( common.wfactstep) end,
{ description = "Increase master width factor", group = "Layout" }
},
{
{ "Mod4" }, "h", function () awful.tag.incmwfact(-common.wfactstep) end,
{ description = "Decrease master width factor", group = "Layout" }
},
{
{ "Mod4", "Shift" }, "h", function () awful.tag.incnmaster( 1, nil, true) end,
{ description = "Increase the number of master clients", group = "Layout" }
},
{
{ "Mod4", "Shift" }, "l", function () awful.tag.incnmaster(-1, nil, true) end,
{ description = "Decrease the number of master clients", group = "Layout" }
},
{
{ "Mod4", "Control" }, "h", function () awful.tag.incncol( 1, nil, true) end,
{ description = "Increase the number of columns", group = "Layout" }
},
{
{ "Mod4", "Control" }, "l", function () awful.tag.incncol(-1, nil, true) end,
{ description = "Decrease the number of columns", group = "Layout" }
},
}
common.keys.corner = {
{
{ "Mod4" }, "l", function () awful.tag.incmwfact( common.wfactstep) end,
{ description = "Increase master width factor", group = "Layout" }
},
{
{ "Mod4" }, "h", function () awful.tag.incmwfact(-common.wfactstep) end,
{ description = "Decrease master width factor", group = "Layout" }
},
{
{ "Mod4", "Shift" }, "h", function () awful.tag.incnmaster( 1, nil, true) end,
{ description = "Increase the number of master clients", group = "Layout" }
},
{
{ "Mod4", "Shift" }, "l", function () awful.tag.incnmaster(-1, nil, true) end,
{ description = "Decrease the number of master clients", group = "Layout" }
},
}
common.keys.magnifier = {
{
{ "Mod4" }, "l", function () awful.tag.incmwfact( common.wfactstep) end,
{ description = "Increase master width factor", group = "Layout" }
},
{
{ "Mod4" }, "h", function () awful.tag.incmwfact(-common.wfactstep) end,
{ description = "Decrease master width factor", group = "Layout" }
},
{
{ "Mod4" }, "g", function () awful.client.setmaster(client.focus) end,
{ description = "Set focused client as master", group = "Movement" }
},
}
-- TODO: set real keyset from navigator theme
common.keys._fake = {
{
{ "Mod4" }, "N1 N2", nil,
{
description = "Swap clients by key", group = "Movement",
keyset = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" }
}
},
{
{ "Mod4" }, "N1 N1", nil,
{
description = "Focus client by key", group = "Action",
keyset = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" }
}
},
}
-- Common handler actions
-----------------------------------------------------------------------------------------------------------------------
common.action = {}
function common.action.exit()
navigator:close()
common.last = {}
end
function common.action.kill()
client.focus:kill()
navigator:restart()
common.last.key = nil
end
-- Keys setup
-----------------------------------------------------------------------------------------------------------------------
-- Hotkey tips update functions
--------------------------------------------------------------------------------
common.updates = {}
local function build_base_tip()
return awful.util.table.join(common.keys.swap, common.keys.base, common.keys._fake)
end
local function build_tile_tip()
return awful.util.table.join(common.keys.swap, common.keys.tile, common.keys.base, common.keys._fake)
end
local function build_corner_tip()
return awful.util.table.join(common.keys.swap, common.keys.corner, common.keys.base, common.keys._fake)
end
local function build_magnifier_tip()
return awful.util.table.join(common.keys.magnifier, common.keys.base, common.keys._fake)
end
local function set_corner_tip()
common.tips[alayout.suit.corner.nw] = build_corner_tip()
common.tips[alayout.suit.corner.ne] = build_corner_tip()
common.tips[alayout.suit.corner.sw] = build_corner_tip()
common.tips[alayout.suit.corner.se] = build_corner_tip()
end
local function set_tile_tip()
common.tips[alayout.suit.tile] = build_tile_tip()
common.tips[alayout.suit.tile.right] = build_tile_tip()
common.tips[alayout.suit.tile.left] = build_tile_tip()
common.tips[alayout.suit.tile.top] = build_tile_tip()
common.tips[alayout.suit.tile.bottom] = build_tile_tip()
end
common.updates.swap = function()
common.tips[alayout.suit.fair] = build_base_tip()
common.tips[alayout.suit.spiral] = build_base_tip()
common.tips[alayout.suit.spiral.dwindle] = build_base_tip()
set_tile_tip()
set_corner_tip()
end
common.updates.base = function()
common.tips[alayout.suit.fair] = build_base_tip()
common.tips[alayout.suit.spiral] = build_base_tip()
common.tips[alayout.suit.spiral.dwindle] = build_base_tip()
common.tips[alayout.suit.magnifier] = build_magnifier_tip()
set_tile_tip()
set_corner_tip()
end
common.updates.magnifier = function()
common.tips[alayout.suit.magnifier] = build_magnifier_tip()
end
common.updates.tile = function()
set_tile_tip()
end
common.updates.corner = function()
set_corner_tip()
end
-- Keys setup function
--------------------------------------------------------------------------------
function common:set_keys(keys, layout)
if keys then common.keys[layout] = keys end -- update keys
if self.updates[layout] then self.updates[layout]() end -- update tips
end
-- Shared keyboard handlers
-----------------------------------------------------------------------------------------------------------------------
common.grabbers = {}
-- Base grabbers
--------------------------------------------------------------------------------
common.grabbers.base = function(mod, key)
for _, k in ipairs(common.keys.base) do
if redutil.key.match_grabber(k, mod, key) then k[3](); return true end
end
-- if numkey pressed
local index = awful.util.table.hasitem(navigator.style.num, key)
-- swap or focus client
if index then
if navigator.data[index] and awful.util.table.hasitem(navigator.cls, navigator.data[index].client) then
if common.last.key then
if common.last.key == index then
client.focus = navigator.data[index].client
client.focus:raise()
else
redutil.client.swap(navigator.data[common.last.key].client, navigator.data[index].client)
end
common.last.key = nil
else
common.last.key = index
end
return true
end
end
end
common.grabbers.swap = function(mod, key)
for _, k in ipairs(common.keys.swap) do
if redutil.key.match_grabber(k, mod, key) then k[3](); return true end
end
end
common.grabbers.tile = function(mod, key)
for _, k in ipairs(common.keys.tile) do
if redutil.key.match_grabber(k, mod, key) then k[3](); return true end
end
end
common.grabbers.corner = function(mod, key)
for _, k in ipairs(common.keys.corner) do
if redutil.key.match_grabber(k, mod, key) then k[3](); return true end
end
end
common.grabbers.magnifier = function(mod, key)
for _, k in ipairs(common.keys.magnifier) do
if redutil.key.match_grabber(k, mod, key) then k[3](); return true end
end
end
-- Grabbers for awful layouts
--------------------------------------------------------------------------------
local function fair_handler(mod, key, event)
if event == "press" then return end
if common.grabbers.swap(mod, key, event) then return end
if common.grabbers.base(mod, key, event) then return end
end
local function magnifier_handler(mod, key, event)
if event == "press" then return end
if common.grabbers.magnifier(mod, key, event) then return end
if common.grabbers.base(mod, key, event) then return end
end
local function tile_handler(mod, key, event)
if event == "press" then return end
if common.grabbers.tile(mod, key, event) then return end
if common.grabbers.swap(mod, key, event) then return end
if common.grabbers.base(mod, key, event) then return end
end
local function corner_handler(mod, key, event)
if event == "press" then return end
if common.grabbers.corner(mod, key, event) then return end
if common.grabbers.swap(mod, key, event) then return end
if common.grabbers.base(mod, key, event) then return end
end
-- Handlers table
-----------------------------------------------------------------------------------------------------------------------
common.handler[alayout.suit.fair] = fair_handler
common.handler[alayout.suit.spiral] = fair_handler
common.handler[alayout.suit.magnifier] = magnifier_handler
common.handler[alayout.suit.tile] = tile_handler
common.handler[alayout.suit.tile.right] = tile_handler
common.handler[alayout.suit.tile.left] = tile_handler
common.handler[alayout.suit.tile.top] = tile_handler
common.handler[alayout.suit.tile.bottom] = tile_handler
common.handler[alayout.suit.corner.nw] = corner_handler
common.handler[alayout.suit.corner.ne] = corner_handler
common.handler[alayout.suit.corner.se] = corner_handler
common.handler[alayout.suit.corner.sw] = corner_handler
common.handler[alayout.suit.spiral.dwindle] = fair_handler
-- tip dirty setup
common:set_keys(nil, "base")
-- Slightly changed awful mouse move handler
-----------------------------------------------------------------------------------------------------------------------
function common.mouse.move(c, context, hints)
-- Quit if it isn't a mouse.move on a tiled layout, that's handled elsewhere (WHERE?)
if c.floating then return end
if context ~= "mouse.move" then return end
-- move to screen with mouse
if mouse.screen ~= c.screen then c.screen = mouse.screen end
-- check if cutstom layout hadler availible
local l = c.screen.selected_tag and c.screen.selected_tag.layout or nil
if l == awful.layout.suit.floating then return end
if l and l.move_handler then
l.move_handler(c, context, hints)
return
end
-- general handler for tile layouts
local c_u_m = mouse.current_client
if c_u_m and not c_u_m.floating then
if c_u_m ~= c then c:swap(c_u_m) end
end
end
-- End
-----------------------------------------------------------------------------------------------------------------------
return common

View File

@ -0,0 +1,480 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat grid layout --
-----------------------------------------------------------------------------------------------------------------------
-- Floating layout with discrete geometry
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local beautiful = require("beautiful")
local ipairs = ipairs
local pairs = pairs
local math = math
local unpack = unpack or table.unpack
local awful = require("awful")
local common = require("redflat.layout.common")
local redutil = require("redflat.util")
local hasitem = awful.util.table.hasitem
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local grid = { data = {} }
grid.name = "grid"
-- default keys
grid.keys = {}
grid.keys.move = {
{
{ "Mod4" }, "Up", function() grid.move_to("up") end,
{ description = "Move window up", group = "Movement" }
},
{
{ "Mod4" }, "Down", function() grid.move_to("down") end,
{ description = "Move window down", group = "Movement" }
},
{
{ "Mod4" }, "Left", function() grid.move_to("left") end,
{ description = "Move window left", group = "Movement" }
},
{
{ "Mod4" }, "Right", function() grid.move_to("right") end,
{ description = "Move window right", group = "Movement" }
},
{
{ "Mod4", "Control" }, "Up", function() grid.move_to("up", true) end,
{ description = "Move window up by bound", group = "Movement" }
},
{
{ "Mod4", "Control" }, "Down", function() grid.move_to("down", true) end,
{ description = "Move window down by bound", group = "Movement" }
},
{
{ "Mod4", "Control" }, "Left", function() grid.move_to("left", true) end,
{ description = "Move window left by bound", group = "Movement" }
},
{
{ "Mod4", "Control" }, "Right", function() grid.move_to("right", true) end,
{ description = "Move window right by bound", group = "Movement" }
},
}
grid.keys.resize = {
{
{ "Mod4" }, "k", function() grid.resize_to("up") end,
{ description = "Inrease window size to the up", group = "Resize" }
},
{
{ "Mod4" }, "j", function() grid.resize_to("down") end,
{ description = "Inrease window size to the down", group = "Resize" }
},
{
{ "Mod4" }, "h", function() grid.resize_to("left") end,
{ description = "Inrease window size to the left", group = "Resize" }
},
{
{ "Mod4" }, "l", function() grid.resize_to("right") end,
{ description = "Inrease window size to the right", group = "Resize" }
},
{
{ "Mod4", "Shift" }, "k", function() grid.resize_to("up", nil, true) end,
{ description = "Decrease window size from the up", group = "Resize" }
},
{
{ "Mod4", "Shift" }, "j", function() grid.resize_to("down", nil, true) end,
{ description = "Decrease window size from the down", group = "Resize" }
},
{
{ "Mod4", "Shift" }, "h", function() grid.resize_to("left", nil, true) end,
{ description = "Decrease window size from the left", group = "Resize" }
},
{
{ "Mod4", "Shift" }, "l", function() grid.resize_to("right", nil, true) end,
{ description = "Decrease window size from the right", group = "Resize" }
},
{
{ "Mod4", "Control" }, "k", function() grid.resize_to("up", true) end,
{ description = "Increase window size to the up by bound", group = "Resize" }
},
{
{ "Mod4", "Control" }, "j", function() grid.resize_to("down", true) end,
{ description = "Increase window size to the down by bound", group = "Resize" }
},
{
{ "Mod4", "Control" }, "h", function() grid.resize_to("left", true) end,
{ description = "Increase window size to the left by bound", group = "Resize" }
},
{
{ "Mod4", "Control" }, "l", function() grid.resize_to("right", true) end,
{ description = "Increase window size to the right by bound", group = "Resize" }
},
{
{ "Mod4", "Control", "Shift" }, "k", function() grid.resize_to("up", true, true) end,
{ description = "Decrease window size from the up by bound ", group = "Resize" }
},
{
{ "Mod4", "Control", "Shift" }, "j", function() grid.resize_to("down", true, true) end,
{ description = "Decrease window size from the down by bound ", group = "Resize" }
},
{
{ "Mod4", "Control", "Shift" }, "h", function() grid.resize_to("left", true, true) end,
{ description = "Decrease window size from the left by bound ", group = "Resize" }
},
{
{ "Mod4", "Control", "Shift" }, "l", function() grid.resize_to("right", true, true) end,
{ description = "Decrease window size from the right by bound ", group = "Resize" }
},
}
grid.keys.all = awful.util.table.join(grid.keys.move, grid.keys.resize)
-- Support functions
-----------------------------------------------------------------------------------------------------------------------
local function compare(a ,b) return a < b end
-- Find all rails for given client
------------------------------------------------------------
local function get_rail(c)
local wa = screen[c.screen].workarea
local cls = awful.client.visible(c.screen)
local rail = { x = { wa.x, wa.x + wa.width }, y = { wa.y, wa.y + wa.height } }
table.remove(cls, hasitem(cls, c))
for _, v in ipairs(cls) do
local lg = redutil.client.fullgeometry(v)
local xr = lg.x + lg.width
local yb = lg.y + lg.height
if not hasitem(rail.x, lg.x) then table.insert(rail.x, lg.x) end
if not hasitem(rail.x, xr) then table.insert(rail.x, xr) end
if not hasitem(rail.y, lg.y) then table.insert(rail.y, lg.y) end
if not hasitem(rail.y, yb) then table.insert(rail.y, yb) end
end
table.sort(rail.x, compare)
table.sort(rail.y, compare)
return rail
end
local function update_rail(c) grid.data.rail = get_rail(c) end
-- Calculate cell geometry
------------------------------------------------------------
local function make_cell(wa, cellnum)
local cell = {
x = wa.width / cellnum.x,
y = wa.height / cellnum.y
}
-- adapt cell table to work with geometry prop
cell.width = cell.x
cell.height = cell.y
return cell
end
-- Grid rounding
------------------------------------------------------------
local function round(a, n)
return n * math.floor((a + n / 2) / n)
end
-- Fit client into grid
------------------------------------------------------------
local function fit_cell(g, cell)
local ng = {}
for k, v in pairs(g) do
ng[k] = math.ceil(round(v, cell[k]))
end
return ng
end
-- Check geometry difference
------------------------------------------------------------
local function is_diff(g1, g2, cell)
for k, v in pairs(g1) do
if math.abs(g2[k] - v) >= cell[k] then return true end
end
return false
end
-- Move client
-----------------------------------------------------------------------------------------------------------------------
function grid.move_to(dir, is_rail, k)
local ng = {}
local data = grid.data
local c = client.focus
if not c then return end
if data.last ~= c then
data.last = c
update_rail(c)
end
local g = redutil.client.fullgeometry(c)
k = k or 1
if dir == "left" then
if is_rail then
for i = #data.rail.x, 1, - 1 do
if data.rail.x[i] < g.x then
ng.x = data.rail.x[i]
break
end
end
else
ng.x = g.x - data.cell.x * k
end
elseif dir == "right" then
if is_rail then
for i = 1, #data.rail.x do
if data.rail.x[i] > g.x + g.width + 1 then
ng.x = data.rail.x[i] - g.width
break
end
end
else
ng.x = g.x + data.cell.x * k
end
elseif dir == "up" then
if is_rail then
for i = #data.rail.y, 1, - 1 do
if data.rail.y[i] < g.y then
ng.y = data.rail.y[i]
break
end
end
else
ng.y = g.y - data.cell.y * k
end
elseif dir == "down" then
if is_rail then
for i = 1, #data.rail.y do
if data.rail.y[i] > g.y + g.height + 1 then
ng.y = data.rail.y[i] - g.height
break
end
end
else
ng.y = g.y + data.cell.y * k
end
end
redutil.client.fullgeometry(c, ng)
end
-- Resize client
-----------------------------------------------------------------------------------------------------------------------
grid.resize_to = function(dir, is_rail, is_reverse)
local ng = {}
local c = client.focus
local data = grid.data
if not c then return end
if data.last ~= c then
data.last = c
update_rail(c)
end
local g = redutil.client.fullgeometry(c)
local sign = is_reverse and -1 or 1
if dir == "up" then
if is_rail then
-- select loop direction (from min to max or from max to min)
local f, l, s = unpack(is_reverse and { 1, #data.rail.y, 1 } or { #data.rail.y, 1, - 1 })
for i = f, l, s do
if is_reverse and data.rail.y[i] > g.y or not is_reverse and data.rail.y[i] < g.y then
ng = { y = data.rail.y[i], height = g.height + g.y - data.rail.y[i] }
break
end
end
else
ng = { y = g.y - sign * data.cell.y, height = g.height + sign * data.cell.y }
end
elseif dir == "down" then
if is_rail then
local f, l, s = unpack(is_reverse and { #data.rail.y, 1, - 1 } or { 1, #data.rail.y, 1 })
for i = f, l, s do
if is_reverse and data.rail.y[i] < (g.y + g.height - 1)
or not is_reverse and data.rail.y[i] > (g.y + g.height + 1) then
ng = { height = data.rail.y[i] - g.y }
break
end
end
else
ng = { height = g.height + sign * data.cell.y }
end
elseif dir == "left" then
if is_rail then
local f, l, s = unpack(is_reverse and { 1, #data.rail.x, 1 } or { #data.rail.x, 1, - 1 })
for i = f, l, s do
if is_reverse and data.rail.x[i] > g.x or not is_reverse and data.rail.x[i] < g.x then
ng = { x = data.rail.x[i], width = g.width + g.x - data.rail.x[i] }
break
end
end
else
ng = { x = g.x - sign * data.cell.x, width = g.width + sign * data.cell.x }
end
elseif dir == "right" then
if is_rail then
local f, l, s = unpack(is_reverse and { #data.rail.x, 1, - 1 } or { 1, #data.rail.x, 1 })
for i = f, l, s do
if is_reverse and data.rail.x[i] < (g.x + g.width)
or not is_reverse and data.rail.x[i] > (g.x + g.width + 1) then
ng = { width = data.rail.x[i] - g.x }
break
end
end
else
ng = { width = g.width + sign * data.cell.x }
end
end
redutil.client.fullgeometry(c, ng)
end
-- Keygrabber
-----------------------------------------------------------------------------------------------------------------------
grid.maingrabber = function(mod, key)
for _, k in ipairs(grid.keys.all) do
if redutil.key.match_grabber(k, mod, key) then k[3](); return true end
end
end
grid.key_handler = function (mod, key, event)
if event == "press" then return end
if grid.maingrabber(mod, key, event) then return end
if common.grabbers.base(mod, key, event) then return end
end
-- Tile function
-----------------------------------------------------------------------------------------------------------------------
function grid.arrange(p)
-- theme vars
local cellnum = beautiful.cellnum or { x = 100, y = 60 }
-- aliases
local wa = p.workarea
local cls = p.clients
-- calculate cell
-- fix useless gap correction?
grid.data.cell = make_cell({ width = wa.width + 2 * p.useless_gap, height = wa.height + 2 * p.useless_gap }, cellnum)
-- nothing to tile here
if #cls == 0 then return end
-- tile
for _, c in ipairs(cls) do
local g = redutil.client.fullgeometry(c)
g = fit_cell(g, grid.data.cell)
redutil.client.fullgeometry(c, g)
end
end
-- Mouse moving function
-----------------------------------------------------------------------------------------------------------------------
function grid.move_handler(c, _, hints)
local g = redutil.client.fullgeometry(c)
local hg = { x = hints.x, y = hints.y, width = g.width, height = g.height }
if is_diff(hg, g, grid.data.cell) then
redutil.client.fullgeometry(c, fit_cell(hg, grid.data.cell))
end
end
-- Mouse resizing function
-----------------------------------------------------------------------------------------------------------------------
function grid.mouse_resize_handler(c, corner)
local g = redutil.client.fullgeometry(c)
local cg = g
-- set_mouse_on_corner(g, corner)
mousegrabber.run(
function (_mouse)
for _, v in ipairs(_mouse.buttons) do
if v then
local ng
if corner == "bottom_right" then
ng = {
width = _mouse.x - g.x,
height = _mouse.y - g.y
}
elseif corner == "bottom_left" then
ng = {
x = _mouse.x,
width = (g.x + g.width) - _mouse.x,
height = _mouse.y - g.y
}
elseif corner == "top_left" then
ng = {
x = _mouse.x,
y = _mouse.y,
width = (g.x + g.width) - _mouse.x,
height = (g.y + g.height) - _mouse.y
}
else
ng = {
y = _mouse.y,
width = _mouse.x - g.x,
height = (g.y + g.height) - _mouse.y
}
end
if ng.width <= 0 then ng.width = nil end
if ng.height <= 0 then ng.height = nil end
-- if c.maximized_horizontal then ng.width = g.width ng.x = g.x end
-- if c.maximized_vertical then ng.height = g.height ng.y = g.y end
if is_diff(ng, cg, grid.data.cell) then
cg = redutil.client.fullgeometry(c, fit_cell(ng, grid.data.cell))
end
return true
end
end
return false
end,
corner .. "_corner"
)
end
-- Redflat navigator support functions
-----------------------------------------------------------------------------------------------------------------------
function grid:set_keys(keys, layout)
layout = layout or "all"
if keys then
self.keys[layout] = keys
if layout ~= "all" then grid.keys.all = awful.util.table.join(grid.keys.move, grid.keys.resize) end
end
self.tip = awful.util.table.join(self.keys.all, common.keys.base)
end
function grid.startup()
if not grid.tip then grid:set_keys() end
end
function grid.cleanup()
grid.data.last = nil
end
-- End
-----------------------------------------------------------------------------------------------------------------------
return grid

View File

@ -0,0 +1,10 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat library --
-----------------------------------------------------------------------------------------------------------------------
local wrequire = require("redflat.util").wrequire
local setmetatable = setmetatable
local lib = { _NAME = "redflat.layout" }
return setmetatable(lib, { __index = wrequire })

View File

@ -0,0 +1,688 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat map layout --
-----------------------------------------------------------------------------------------------------------------------
-- Tiling with user defined geometry
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local ipairs = ipairs
local pairs = pairs
local math = math
local unpack = unpack or table.unpack
local awful = require("awful")
local timer = require("gears.timer")
local redflat = require("redflat")
local redutil = require("redflat.util")
local common = require("redflat.layout.common")
local rednotify = require("redflat.float.notify")
local hasitem = awful.util.table.hasitem
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local map = { data = setmetatable({}, { __mode = "k" }), scheme = setmetatable({}, { __mode = "k" }), keys = {} }
map.name = "usermap"
map.notification = true
map.notification_style = {}
local hitimer
map.hilight_timeout = 0.2
-- default keys
map.keys.layout = {
{
{ "Mod4" }, "s", function() map.swap_group() end,
{ description = "Change placement direction for group", group = "Layout" }
},
{
{ "Mod4" }, "v", function() map.new_group(true) end,
{ description = "Create new vertical group", group = "Layout" }
},
{
{ "Mod4" }, "b", function() map.new_group(false) end,
{ description = "Create new horizontal group", group = "Layout" }
},
{
{ "Mod4", "Control" }, "v", function() map.insert_group(true) end,
{ description = "Insert new vertical group before active", group = "Layout" }
},
{
{ "Mod4", "Control" }, "b", function() map.insert_group(false) end,
{ description = "Insert new horizontal group before active", group = "Layout" }
},
{
{ "Mod4" }, "d", function() map.delete_group() end,
{ description = "Destroy group", group = "Layout" }
},
{
{ "Mod4", "Control" }, "d", function() map.clean_groups() end,
{ description = "Destroy all empty groups", group = "Layout" }
},
{
{ "Mod4" }, "a", function() map.set_active() end,
{ description = "Set active group", group = "Layout" }
},
{
{ "Mod4" }, "f", function() map.move_to_active() end,
{ description = "Move focused client to active group", group = "Layout" }
},
{
{ "Mod4", "Control" }, "a", function() map.hilight_active() end,
{ description = "Hilight active group", group = "Layout" }
},
{
{ "Mod4" }, ".", function() map.switch_active(1) end,
{ description = "Activate next group", group = "Layout" }
},
{
{ "Mod4" }, ",", function() map.switch_active(-1) end,
{ description = "Activate previous group", group = "Layout" }
},
{
{ "Mod4" }, "]", function() map.move_group(1) end,
{ description = "Move active group to the top", group = "Layout" }
},
{
{ "Mod4" }, "[", function() map.move_group(-1) end,
{ description = "Move active group to the bottom", group = "Layout" }
},
{
{ "Mod4" }, "r", function() map.reset_tree() end,
{ description = "Reset layout structure", group = "Layout" }
},
}
map.keys.resize = {
{
{ "Mod4" }, "h", function() map.incfactor(nil, 0.1, false) end,
{ description = "Increase window horizontal size factor", group = "Resize" }
},
{
{ "Mod4" }, "l", function() map.incfactor(nil, -0.1, false) end,
{ description = "Decrease window horizontal size factor", group = "Resize" }
},
{
{ "Mod4" }, "k", function() map.incfactor(nil, 0.1, true) end,
{ description = "Increase window vertical size factor", group = "Resize" }
},
{
{ "Mod4" }, "j", function() map.incfactor(nil, -0.1, true) end,
{ description = "Decrease window vertical size factor", group = "Resize" }
},
{
{ "Mod4", "Control" }, "h", function() map.incfactor(nil, 0.1, false, true) end,
{ description = "Increase group horizontal size factor", group = "Resize" }
},
{
{ "Mod4", "Control" }, "l", function() map.incfactor(nil, -0.1, false, true) end,
{ description = "Decrease group horizontal size factor", group = "Resize" }
},
{
{ "Mod4", "Control" }, "k", function() map.incfactor(nil, 0.1, true, true) end,
{ description = "Increase group vertical size factor", group = "Resize" }
},
{
{ "Mod4", "Control" }, "j", function() map.incfactor(nil, -0.1, true, true) end,
{ description = "Decrease group vertical size factor", group = "Resize" }
},
}
map.keys.all = awful.util.table.join(map.keys.layout, map.keys.resize)
-- Support functions
-----------------------------------------------------------------------------------------------------------------------
-- Layout action notifications
--------------------------------------------------------------------------------
local function notify(txt)
if map.notification then rednotify:show(redutil.table.merge({ text = txt }, map.notification_style)) end
end
-- Calculate geometry for single client or group
--------------------------------------------------------------------------------
local function cut_geometry(wa, is_vertical, size)
if is_vertical then
local g = { x = wa.x, y = wa.y, width = wa.width, height = size }
wa.y = wa.y + size
return g
else
local g = { x = wa.x, y = wa.y, width = size, height = wa.height }
wa.x = wa.x + size
return g
end
end
-- Build container for single client or group
--------------------------------------------------------------------------------
function map.construct_itempack(cls, wa, is_vertical, parent)
local pack = { items = {}, wa = wa, cls = { unpack(cls) }, is_vertical = is_vertical, parent = parent }
-- Create pack of items with base properties
------------------------------------------------------------
for i, c in ipairs(cls) do
pack.items[i] = { client = c, child = nil, factor = 1 }
end
-- Update pack clients
------------------------------------------------------------
function pack:set_cls(clist)
local current = { unpack(clist) }
-- update existing items, remove overage if need
for i, item in ipairs(self.items) do
if not item.child then
if #current > 0 then
self.items[i].client = current[1]
table.remove(current, 1)
else
self.items[i] = nil
end
end
end
-- create additional items if need
for _, c in ipairs(current) do
self.items[#self.items + 1] = { client = c, child = nil, factor = 1 }
end
end
-- Get current pack clients
------------------------------------------------------------
function pack:get_cls()
local clist = {}
for _, item in ipairs(self.items) do if not item.child then clist[#clist + 1] = item.client end end
return clist
end
-- Update pack geometry
------------------------------------------------------------
function pack:set_wa(workarea)
self.wa = workarea
end
-- Get number of items reserved for single client only
------------------------------------------------------------
function pack:get_places()
local n = 0
for _, item in ipairs(self.items) do if not item.child then n = n + 1 end end
return n
end
-- Get child index
------------------------------------------------------------
function pack:get_child_id(pack_)
for i, item in ipairs(self.items) do
if item.child == pack_ then return i end
end
end
-- Check if container with inheritors keep any real client
------------------------------------------------------------
function pack:is_filled()
local filled = false
for _, item in ipairs(self.items) do
if not item.child then
return true
else
filled = filled or item.child:is_filled()
end
end
return filled
end
-- Increase window size factor for item with index
------------------------------------------------------------
function pack:incfacror(index, df, vertical)
if vertical == self.is_vertical then
self.items[index].factor = math.max(self.items[index].factor + df, 0.1)
elseif self.parent then
local pi = self.parent:get_child_id(self)
self.parent:incfacror(pi, df, vertical)
end
end
-- Recalculate geometry for every item in container
------------------------------------------------------------
function pack:rebuild()
-- vars
local geometries = {}
local weight = 0
local area = awful.util.table.clone(self.wa)
local direction = self.is_vertical and "height" or "width"
-- check factor norming
for _, item in ipairs(self.items) do
if not item.child or item.child:is_filled() then weight = weight + item.factor end
end
if weight == 0 then return geometries end
-- geomentry calculation
for i, item in ipairs(self.items) do
if not item.child or item.child:is_filled() then
local size = self.wa[direction] / weight * item.factor
local g = cut_geometry(area, self.is_vertical, size, i)
if item.child then
item.child:set_wa(g)
else
geometries[item.client] = g
end
end
end
return geometries
end
return pack
end
-- Build layout tree
--------------------------------------------------------------------------------
local function construct_tree(wa, t)
-- Initial structure on creation
------------------------------------------------------------
local tree = map.scheme[t] and map.scheme[t].construct(wa) or map.base_construct(wa)
-- Find pack contaner for client
------------------------------------------------------------
function tree:get_pack(c)
for _, pack in ipairs(self.set) do
for i, item in ipairs(pack.items) do
if not item.child and c == item.client then return pack, i end
end
end
end
-- Create new contaner in place of client
------------------------------------------------------------
function tree:create_group(c, is_vertical)
local parent, index = self:get_pack(c)
local new_pack = map.construct_itempack({}, {}, is_vertical, parent)
self.set[#self.set + 1] = new_pack
parent.items[index] = { child = new_pack, factor = 1 }
self.active = #self.set
awful.client.setslave(c)
notify("New " .. (is_vertical and "vertical" or "horizontal") .. " group")
end
-- Insert new contaner in before active
------------------------------------------------------------
function tree:insert_group(is_vertical)
local pack = self.set[self.active]
local new_pack = map.construct_itempack({}, pack.wa, is_vertical, pack.parent)
if pack.parent then
for _, item in ipairs(pack.parent.items) do
if item.child == pack then item.child = new_pack; break end
end
end
new_pack.items[1] = { child = pack, factor = 1, client = nil }
table.insert(self.set, self.active, new_pack)
pack.parent = new_pack
notify("New " .. (is_vertical and "vertical" or "horizontal") .. " group")
end
-- Destroy the given container
------------------------------------------------------------
function tree:delete_group(pack)
pack = pack or self.set[self.active]
local index = hasitem(self.set, pack)
local has_child = pack:get_places() < #pack.items
-- some containers can't be destroyed
-- root container
if #self.set == 1 then
notify("Cant't destroy last group")
return
end
-- container with many inheritors
if has_child and #pack.items > 1 then
notify("Cant't destroy group with inheritors")
return
end
-- disconnect container from parent
if pack.parent then
for i, item in ipairs(pack.parent.items) do
if item.child == pack then
if has_child then
-- container in container case
-- inheritor can be transmit to parent without changing geometry
item.child = pack.items[1].child
else
-- container without inheritors can be safaly destroyed
table.remove(pack.parent.items, i)
end
break
end
end
end
-- destroy container
table.remove(self.set, index)
if not self.set[self.active] then self.active = #self.set end
notify("Group " .. tostring(index) .. " destroyed")
end
-- Destroy all empty containers
------------------------------------------------------------
function tree:cleanup()
for i = #self.set, 1, -1 do
if #self.set[i].items == 0 then
tree:delete_group(self.set[i])
elseif #self.set[i].items == 1 and self.set[i].items[1].child then
self.set[i].items[1].child.wa = self.set[i].wa
tree:delete_group(self.set[i])
end
end
end
-- Recalculate geometry for whole layout
------------------------------------------------------------
function tree:rebuild(clist)
local current = { unpack(clist) }
local geometries = {}
-- distributing clients among existing contaners
for _, pack in ipairs(self.set) do
local n = pack:get_places()
local chunk = { unpack(current, 1, n) }
current = { unpack(current, n + 1) }
pack:set_cls(chunk)
end
-- distributing clients among existing contaners
if #current > 0 then
for _, c in ipairs(current) do
if self.autoaim then self.active = self:aim() end
local refill = awful.util.table.join(self.set[self.active]:get_cls(), { c })
self.set[self.active]:set_cls(refill)
end
-- local refill = awful.util.table.join(self.set[self.active]:get_cls(), current)
-- self.set[self.active]:set_cls(refill)
end
-- recalculate geomery for every container in tree
for _, pack in ipairs(self.set) do
geometries = awful.util.table.join(geometries, pack:rebuild())
end
return geometries
end
return tree
end
-- Layout manipulation functions
-----------------------------------------------------------------------------------------------------------------------
-- Change container placement direction
--------------------------------------------------------------------------------
function map.swap_group()
local c = client.focus
if not c then return end
local t = c.screen.selected_tag
local pack = map.data[t]:get_pack(c)
pack.is_vertical = not pack.is_vertical
t:emit_signal("property::layout")
end
-- Create new container for client
--------------------------------------------------------------------------------
function map.new_group(is_vertical)
local c = client.focus
if not c then return end
local t = c.screen.selected_tag
map.data[t].autoaim = false
map.data[t]:create_group(c, is_vertical)
if hitimer then return end
hitimer = timer({ timeout = map.hilight_timeout })
hitimer:connect_signal("timeout",
function()
redflat.service.navigator.hilight.show(map.data[t].set[map.data[t].active].wa)
hitimer:stop()
hitimer = nil
end
)
hitimer:start() -- autostart option doesn't work?
end
-- Destroy active container
--------------------------------------------------------------------------------
function map.delete_group()
local t = mouse.screen.selected_tag
map.data[t].autoaim = false
map.data[t]:delete_group()
t:emit_signal("property::layout")
end
-- Check if client exist in layout tree
--------------------------------------------------------------------------------
function map.check_client(c)
if c.sticky then return true end
for _, t in ipairs(c:tags()) do
for k, _ in pairs(map.data) do if k == t then return true end end
end
end
-- Remove client from layout tree and change tree structure
--------------------------------------------------------------------------------
function map.clean_client(c)
for t, _ in pairs(map.data) do
local pack, index = map.data[t]:get_pack(c)
if pack then table.remove(pack.items, index) end
end
end
-- Destroy all empty containers
--------------------------------------------------------------------------------
function map.clean_groups()
local t = mouse.screen.selected_tag
map.data[t].autoaim = false
map.data[t]:cleanup()
t:emit_signal("property::layout")
end
-- Set active container (new client will be allocated to this one)
--------------------------------------------------------------------------------
function map.set_active(c)
c = c or client.focus
if not c then return end
local t = c.screen.selected_tag
local pack = map.data[t]:get_pack(c)
if pack then
map.data[t].autoaim = false
map.data[t].active = hasitem(map.data[t].set, pack)
redflat.service.navigator.hilight.show(pack.wa)
notify("Active group index: " .. tostring(map.data[t].active))
end
end
-- Hilight active container (navigetor widget feature)
--------------------------------------------------------------------------------
function map.hilight_active()
local t = mouse.screen.selected_tag
local pack = map.data[t].set[map.data[t].active]
redflat.service.navigator.hilight.show(pack.wa)
end
-- Switch active container by index
--------------------------------------------------------------------------------
function map.switch_active(n)
local t = mouse.screen.selected_tag
local na = map.data[t].active + n
if map.data[t].set[na] then
map.data[t].autoaim = false
map.data[t].active = na
--local pack = map.data[t].set[na]
notify("Active group index: " .. tostring(na))
end
redflat.service.navigator.hilight.show(map.data[t].set[map.data[t].active].wa)
end
-- Move client to active container
--------------------------------------------------------------------------------
function map.move_to_active(c)
c = c or client.focus
if not c then return end
local t = c.screen.selected_tag
local pack, index = map.data[t]:get_pack(c)
if pack then
table.remove(pack.items, index)
awful.client.setslave(c)
end
end
-- Increase window size factor for client
--------------------------------------------------------------------------------
function map.incfactor(c, df, is_vertical, on_group)
c = c or client.focus
if not c then return end
local t = c.screen.selected_tag
local pack, index = map.data[t]:get_pack(c)
if not pack then return end -- fix this?
if on_group and pack.parent then
index = pack.parent:get_child_id(pack)
pack = pack.parent
end
if pack then
pack:incfacror(index, df, is_vertical)
t:emit_signal("property::layout")
end
end
-- Move element inside his container
--------------------------------------------------------------------------------
function map.move_group(dn)
local t = mouse.screen.selected_tag
local pack = map.data[t].set[map.data[t].active]
if pack.parent then
map.data[t].autoaim = false
local i = pack.parent:get_child_id(pack)
if pack.parent.items[i + dn] then
pack.parent.items[i], pack.parent.items[i + dn] = pack.parent.items[i + dn], pack.parent.items[i]
t:emit_signal("property::layout")
end
end
end
-- Insert new group before active
--------------------------------------------------------------------------------
function map.insert_group(is_vertical)
local t = mouse.screen.selected_tag
map.data[t].autoaim = false
map.data[t]:insert_group(is_vertical)
t:emit_signal("property::layout")
end
-- Reset layout structure
--------------------------------------------------------------------------------
function map.reset_tree()
local t = mouse.screen.selected_tag
map.data[t] = nil
t:emit_signal("property::layout")
end
-- Base layout scheme
-----------------------------------------------------------------------------------------------------------------------
-- TODO: fix unused arg
function map.base_set_new_pack(cls, wa, _, parent, factor)
local pack = map.construct_itempack(cls, wa, true, parent)
table.insert(parent.items, { child = pack, factor = factor or 1 })
return pack
end
map.base_autoaim = true
function map.base_aim(tree)
if #tree.set[2].items == 0 then return 2 end
local active = #tree.set[3].items > #tree.set[2].items and 2 or 3
return active
end
function map.base_construct(wa)
local tree = { set = {}, active = 1, autoaim = map.base_autoaim, aim = map.base_aim }
tree.set[1] = map.construct_itempack({}, wa, false)
tree.set[2] = map.base_set_new_pack({}, wa, true, tree.set[1])
tree.set[3] = map.base_set_new_pack({}, wa, true, tree.set[1])
return tree
end
-- Tile function
-----------------------------------------------------------------------------------------------------------------------
function map.arrange(p)
local wa = awful.util.table.clone(p.workarea)
local cls = p.clients
local data = map.data
local t = p.tag or screen[p.screen].selected_tag
-- nothing to tile here
if #cls == 0 then return end
-- init layout tree
if not data[t] then data[t] = construct_tree(wa, t) end
-- tile
p.geometries = data[t]:rebuild(cls)
end
-- Keygrabber
-----------------------------------------------------------------------------------------------------------------------
map.maingrabber = function(mod, key)
for _, k in ipairs(map.keys.all) do
if redutil.key.match_grabber(k, mod, key) then k[3](); return true end
end
end
map.key_handler = function (mod, key, event)
if event == "press" then return end
if map.maingrabber(mod, key) then return end
if common.grabbers.swap(mod, key, event) then return end
if common.grabbers.base(mod, key, event) then return end
end
-- Redflat navigator support functions
-----------------------------------------------------------------------------------------------------------------------
function map:set_keys(keys, layout)
layout = layout or "all"
if keys then
self.keys[layout] = keys
if layout ~= "all" then self.keys.all = awful.util.table.join(self.keys.layout, map.keys.resize) end
end
self.tip = awful.util.table.join(self.keys.all, common.keys.swap, common.keys.base, common.keys._fake)
end
function map.startup()
if not map.tip then map:set_keys() end
end
-- End
-----------------------------------------------------------------------------------------------------------------------
return map

View File

@ -0,0 +1,676 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat menu --
-----------------------------------------------------------------------------------------------------------------------
-- awful.menu modification
-- Custom widget support added
-- Auto hide option added
-- Right icon support added to default item constructor
-- Icon margin added to default item constructor
-- Auto hotkeys for menu items added
-- Horizontal mode support removed
-- Add to index and delete item functions removed
-- menu:clients function removed
-----------------------------------------------------------------------------------------------------------------------
-- Some code was taken from
------ awful.menu v3.5.2
------ (c) 2008, 2011 Damien Leone, Julien Danjou, dodo
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local wibox = require("wibox")
local awful = require("awful")
local beautiful = require("beautiful")
local timer = require("gears.timer")
local setmetatable = setmetatable
local string = string
local ipairs = ipairs
local pcall = pcall
local print = print
local table = table
local type = type
local unpack = unpack or table.unpack
local redutil = require("redflat.util")
local svgbox = require("redflat.gauge.svgbox")
local redtip = require("redflat.float.hotkeys")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local menu = { mt = {}, action = {}, keys = {} }
local _fake_context = { dpi = beautiful.xresources.get_dpi() } -- fix this
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_theme()
local style = {
border_width = 2,
screen_gap = 0,
submenu_icon = redutil.base.placeholder({ txt = "" }),
height = 20,
width = 200,
font = "Sans 12",
icon_margin = { 0, 0, 0, 0 }, -- left icon margin
ricon_margin = { 0, 0, 0, 0 }, -- right icon margin
nohide = false,
auto_expand = true,
auto_hotkey = false,
svg_scale = { false, false },
hide_timeout = 0,
select_first = true,
keytip = { geometry = { width = 400 } },
color = { border = "#575757", text = "#aaaaaa", highlight = "#eeeeee",
main = "#b1222b", wibox = "#202020",
submenu_icon = nil, right_icon = nil, left_icon = nil },
shape = nil
}
return redutil.table.merge(style, beautiful.menu or {})
end
-- Support functions
-----------------------------------------------------------------------------------------------------------------------
-- Check if any menu item connected with given key
-- and run menu item command if found
--------------------------------------------------------------------------------
local function check_access_key(_menu, key)
local num = awful.util.table.hasitem(_menu.keys, key)
if num then
_menu:item_enter(num)
_menu:exec(num)
end
end
-- Get the elder parent of submenu
--------------------------------------------------------------------------------
function menu:get_root()
return self.parent and menu.get_root(self.parent) or self
end
-- Setup case insensitive underline markup to given character in string
--------------------------------------------------------------------------------
local function make_u(text, key)
local pos = key and string.find(string.lower(text), key) or nil
if pos then
local rkey = string.sub(text, pos, pos)
return string.gsub(text, rkey, "<u>" .. rkey .. "</u>", 1)
end
return text
end
-- Function to set menu or submenu in position
-----------------------------------------------------------------------------------------------------------------------
local function set_coords(_menu, screen_idx, m_coords)
local s_geometry = redutil.placement.add_gap(screen[screen_idx].workarea, _menu.theme.screen_gap)
local screen_w = s_geometry.x + s_geometry.width
local screen_h = s_geometry.y + s_geometry.height
local x, y
local b = _menu.wibox.border_width
local w = _menu.wibox.width + 2 * _menu.wibox.border_width
local h = _menu.wibox.height + 2 * _menu.wibox.border_width
if _menu.parent then
local pw = _menu.parent.wibox.width + 2 * _menu.parent.theme.border_width
local piy = _menu.parent.wibox.y + _menu.position + _menu.parent.theme.border_width
y = piy - b
x = _menu.parent.wibox.x + pw
if y + h > screen_h then y = screen_h - h end
if x + w > screen_w then x = _menu.parent.wibox.x - w end
else
if m_coords == nil then
m_coords = mouse.coords()
m_coords.x = m_coords.x - 1
m_coords.y = m_coords.y - 1
end
y = m_coords.y < s_geometry.y and s_geometry.y or m_coords.y
x = m_coords.x < s_geometry.x and s_geometry.x or m_coords.x
if y + h > screen_h then y = screen_h - h end
if x + w > screen_w then x = screen_w - w end
end
_menu.wibox.x = x
_menu.wibox.y = y
end
-- Menu keygrabber
-- A new instance for every submenu should be used
-----------------------------------------------------------------------------------------------------------------------
-- Menu functions
--------------------------------------------------------------------------------
function menu.action.up(_menu, sel)
local sel_new = sel - 1 < 1 and #_menu.items or sel - 1
_menu:item_enter(sel_new)
end
function menu.action.down(_menu, sel)
local sel_new = sel + 1 > #_menu.items and 1 or sel + 1
_menu:item_enter(sel_new)
end
function menu.action.enter(_menu, sel)
if sel > 0 and _menu.items[sel].child then _menu.items[sel].child:show() end
end
function menu.action.exec(_menu, sel)
if sel > 0 then _menu:exec(sel, { exec = true }) end
end
function menu.action.back(_menu)
_menu:hide()
end
function menu.action.close(_menu)
menu.get_root(_menu):hide()
end
-- Menu keys
--------------------------------------------------------------------------------
menu.keys.move = {
{
{}, "Down", menu.action.down,
{ description = "Select next item", group = "Navigation" }
},
{
{}, "Up", menu.action.up,
{ description = "Select previous item", group = "Navigation" }
},
{
{}, "Left", menu.action.back,
{ description = "Go back", group = "Navigation" }
},
{
{}, "Right", menu.action.enter,
{ description = "Open submenu", group = "Navigation" }
},
}
menu.keys.action = {
{
{}, "Escape", menu.action.close,
{ description = "Close menu", group = "Action" }
},
{
{}, "Return", menu.action.exec,
{ description = "Activate item", group = "Action" }
},
{
{ "Mod4" }, "F1", function() redtip:show() end,
{ description = "Show hotkeys helper", group = "Action" }
},
}
menu.keys.all = awful.util.table.join(menu.keys.move, menu.keys.action)
-- this one only displayed in hotkeys helper
menu._fake_keys = {
{
{}, "_letter", nil,
{ description = "Activate item by key", group = "Action" }
},
}
-- Menu keygrabber
--------------------------------------------------------------------------------
local grabber = function(_menu, mod, key, event)
if event ~= "press" then return end
local sel = _menu.sel or 0
for _, k in ipairs(menu.keys.all) do
if redutil.key.match_grabber(k, mod, key) then k[3](_menu, sel); return false end
end
check_access_key(_menu, key)
end
-- Execute menu item
-----------------------------------------------------------------------------------------------------------------------
function menu:exec(num)
local item = self.items[num]
if not item then return end
local cmd = item.cmd
if type(cmd) == "table" then
item.child:show()
elseif type(cmd) == "string" then
if not item.theme.nohide then menu.get_root(self):hide() end
awful.spawn(cmd)
elseif type(cmd) == "function" then
if not item.theme.nohide then menu.get_root(self):hide() end
cmd()
end
end
-- Menu item selection functions
-----------------------------------------------------------------------------------------------------------------------
-- Select item
--------------------------------------------------------------------------------
function menu:item_enter(num, opts)
opts = opts or {}
local item = self.items[num]
if item and self.theme.auto_expand and opts.hover and item.child then
self.items[num].child:show()
end
if num == nil or self.sel == num or not item then
return
elseif self.sel then
self:item_leave(self.sel)
end
item._background:set_fg(item.theme.color.highlight)
item._background:set_bg(item.theme.color.main)
if item.icon and item.theme.color.left_icon then item.icon:set_color(item.theme.color.highlight) end
if item.right_icon and
(item.child and item.theme.color.submenu_icon or not item.child and item.theme.color.right_icon) then
item.right_icon:set_color(item.theme.color.highlight)
end
self.sel = num
end
-- Unselect item
--------------------------------------------------------------------------------
function menu:item_leave(num)
if not num then return end
local item = self.items[num]
if item then
item._background:set_fg(item.theme.color.text)
item._background:set_bg(item.theme.color.wibox)
if item.icon and item.theme.color.left_icon then item.icon:set_color(item.theme.color.left_icon) end
if item.right_icon then
if item.child and item.theme.color.submenu_icon then
-- if there's a child menu, this is a submenu icon
item.right_icon:set_color(item.theme.color.submenu_icon)
elseif item.theme.color.right_icon then
item.right_icon:set_color(item.theme.color.right_icon)
end
end
if item.child then item.child:hide() end
end
end
-- Menu show/hide functions
-----------------------------------------------------------------------------------------------------------------------
-- Show a menu popup.
-- @param args.coords Menu position defaulting to mouse.coords()
--------------------------------------------------------------------------------
function menu:show(args)
args = args or {}
local screen_index = mouse.screen
set_coords(self, screen_index, args.coords)
if self.wibox.visible then return end
-- show menu
awful.keygrabber.run(self._keygrabber)
self.wibox.visible = true
if self.theme.select_first or self.parent then self:item_enter(1) end
-- check hidetimer
if self.hidetimer and self.hidetimer.started then self.hidetimer:stop() end
-- hotkeys helper
-- TODO: optimize code to cache helper (do not rebuild on every show)
local tip
if self.theme.auto_hotkey then
local fk = awful.util.table.clone(menu._fake_keys)
fk[1][4].keyset = self.keys
tip = awful.util.table.join(menu.keys.all, fk)
else
tip = menu.keys.all
end
redtip.cache["Menu"] = nil -- dirty trick to renew helper for every menu instance
redtip:set_pack("Menu", tip, self.theme.keytip.column, self.theme.keytip.geometry)
end
-- Hide a menu popup.
--------------------------------------------------------------------------------
function menu:hide()
if not self.wibox.visible then return end
self:item_leave(self.sel)
if self.sel and self.items[self.sel].child then self.items[self.sel].child:hide() end
self.sel = nil
awful.keygrabber.stop(self._keygrabber)
if self.hidetimer and self.hidetimer.started then self.hidetimer:stop() end
self.wibox.visible = false
redtip:remove_pack()
end
-- Toggle menu visibility
--------------------------------------------------------------------------------
function menu:toggle(args)
if self.wibox.visible then
self:hide()
else
self:show(args)
end
end
-- Set user hotkeys
--------------------------------------------------------------------------------
function menu:set_keys(keys, layout)
layout = layout or "all"
if keys then
self.keys[layout] = keys
if layout ~= "all" then self.keys.all = awful.util.table.join(self.keys.move, self.keys.action) end
end
end
-- Clears all items from the menu
--------------------------------------------------------------------------------
function menu:clear()
self.add_size = 0
self.layout:reset()
self.items = {}
self.keys = {}
self.wibox.height = 1
end
-- Clears and then refills the menu with the given items
--------------------------------------------------------------------------------
function menu:replace_items(items)
self:clear()
for _, item in ipairs(items) do
self:add(item)
end
self.wibox.height = self.add_size > 0 and self.add_size or 1
end
-- Add a new menu entry.
-- args.new (Default: redflat.menu.entry) The menu entry constructor.
-- args.theme (Optional) The menu entry theme.
-- args.* params needed for the menu entry constructor.
-- @param args The item params
-----------------------------------------------------------------------------------------------------------------------
function menu:add(args)
if not args then return end
-- If widget instead of text label recieved
-- just add it to layer, don't try to create menu item
------------------------------------------------------------
if type(args[1]) ~= "string" and args.widget then
local element = {}
element.width, element.height = args.widget:fit(_fake_context, -1, -1)
self.add_size = self.add_size + element.height
self.layout:add(args.widget)
if args.focus then
args.widget:connect_signal(
"mouse::enter",
function()
self:item_leave(self.sel)
self.sel = nil
end
)
end
return
end
-- Set theme for currents item
------------------------------------------------------------
local theme = redutil.table.merge(self.theme, args.theme or {})
args.theme = theme
-- Generate menu item
------------------------------------------------------------
args.new = args.new or menu.entry
local success, item = pcall(args.new, self, args)
if not success then
print("Error while creating menu entry: " .. item)
return
end
if not item.widget then
print("Error while checking menu entry: no property widget found.")
return
end
item.parent = self
item.theme = item.theme or theme
item._background = wibox.container.background(item.widget)
item._background:set_fg(item.theme.color.text)
item._background:set_bg(item.theme.color.wibox)
-- Add item widget to menu layout
------------------------------------------------------------
table.insert(self.items, item)
self.layout:add(item._background)
-- Create bindings
------------------------------------------------------------
local num = #self.items
item._background:buttons(awful.util.table.join(
awful.button({}, 3, function () self:hide() end),
awful.button({}, 1, function ()
self:item_enter(num)
self:exec(num)
end)
))
item.widget:connect_signal("mouse::enter", function() self:item_enter(num, { hover = true }) end)
-- Create submenu if needed
------------------------------------------------------------
if type(args[2]) == "table" then
if not self.items[#self.items].child then
self.items[#self.items].child = menu.new(args[2], self)
self.items[#self.items].child.position = self.add_size
end
end
------------------------------------------------------------
self.add_size = self.add_size + item.theme.height
return item
end
-- Default menu item constructor
-- @param parent The parent menu
-- @param args the item params
-- @return table with all the properties the user wants to change
-----------------------------------------------------------------------------------------------------------------------
function menu.entry(parent, args)
args = args or {}
args.text = args[1] or args.text or ""
args.cmd = args[2] or args.cmd
args.icon = args[3] or args.icon
args.right_icon = args[4] or args.right_icon
-- Create the item label widget
------------------------------------------------------------
local label = wibox.widget.textbox()
label:set_font(args.theme.font)
-- Set hotkey if needed
------------------------------------------------------------
local key
local text = awful.util.escape(args.text)
if args.key and not awful.util.table.hasitem(parent.keys, args.key) then
key = args.key
elseif parent.theme.auto_hotkey then
for i = 1, #text do
local c = string.sub(string.lower(text), i, i)
if not awful.util.table.hasitem(parent.keys, c) and string.match(c, "%l") then
key = c
break
end
end
end
if key then parent.keys[#parent.keys + 1] = key end
label:set_markup(make_u(text, key))
-- Set left icon if needed
------------------------------------------------------------
local iconbox
local margin = wibox.container.margin(label)
if args.icon then
iconbox = svgbox(args.icon, nil, args.theme.color.left_icon)
iconbox:set_vector_resize(args.theme.svg_scale[1])
else
margin:set_left(args.theme.icon_margin[1])
end
-- Set right icon if needed
------------------------------------------------------------
local right_iconbox
if type(args.cmd) == "table" then
right_iconbox = svgbox(args.theme.submenu_icon, nil, args.theme.color.submenu_icon)
right_iconbox:set_vector_resize(args.theme.svg_scale[2])
elseif args.right_icon then
right_iconbox = svgbox(args.right_icon, nil, args.theme.color.right_icon)
right_iconbox:set_vector_resize(args.theme.svg_scale[2])
end
-- Construct item layouts
------------------------------------------------------------
local left = wibox.layout.fixed.horizontal()
if iconbox ~= nil then
left:add(wibox.container.margin(iconbox, unpack(args.theme.icon_margin)))
end
left:add(margin)
local layout = wibox.layout.align.horizontal()
layout:set_left(left)
if right_iconbox ~= nil then
layout:set_right(wibox.container.margin(right_iconbox, unpack(args.theme.ricon_margin)))
end
local layout_const = wibox.container.constraint(layout, "exact", args.theme.width, args.theme.height)
------------------------------------------------------------
return {
label = label,
icon = iconbox,
widget = layout_const,
cmd = args.cmd,
right_icon = right_iconbox
}
end
-- Create new menu
-----------------------------------------------------------------------------------------------------------------------
function menu.new(args, parent)
args = args or {}
-- Initialize menu object
------------------------------------------------------------
local _menu = {
item_enter = menu.item_enter,
item_leave = menu.item_leave,
get_root = menu.get_root,
delete = menu.delete,
toggle = menu.toggle,
hide = menu.hide,
show = menu.show,
exec = menu.exec,
add = menu.add,
clear = menu.clear,
replace_items = menu.replace_items,
items = {},
keys = {},
parent = parent,
layout = wibox.layout.fixed.vertical(),
add_size = 0,
theme = redutil.table.merge(parent and parent.theme or default_theme(), args.theme or {})
}
-- Create items
------------------------------------------------------------
for _, v in ipairs(args) do _menu:add(v) end
if args.items then
for _, v in ipairs(args.items) do _menu:add(v) end
end
_menu._keygrabber = function (...)
grabber(_menu, ...)
end
-- create wibox
------------------------------------------------------------
_menu.wibox = wibox({
type = "popup_menu",
ontop = true,
fg = _menu.theme.color.text,
bg = _menu.theme.color.wibox,
border_color = _menu.theme.color.border,
border_width = _menu.theme.border_width,
shape = _menu.theme.shape
})
_menu.wibox.visible = false
_menu.wibox:set_widget(_menu.layout)
-- set size
_menu.wibox.width = _menu.theme.width
_menu.wibox.height = _menu.add_size > 0 and _menu.add_size or 1
-- Set menu autohide timer
------------------------------------------------------------
if _menu.theme.hide_timeout > 0 then
local root = _menu:get_root()
-- timer only for root menu
-- all submenus will be hidden automatically
if root == _menu then
_menu.hidetimer = timer({ timeout = _menu.theme.hide_timeout })
_menu.hidetimer:connect_signal("timeout", function() _menu:hide() end)
end
-- enter/leave signals for all menu chain
_menu.wibox:connect_signal("mouse::enter",
function()
if root.hidetimer.started then root.hidetimer:stop() end
end)
_menu.wibox:connect_signal("mouse::leave", function() root.hidetimer:start() end)
end
------------------------------------------------------------
return _menu
end
-- Config metatable to call menu module as function
-----------------------------------------------------------------------------------------------------------------------
function menu.mt:__call(...)
return menu.new(...)
end
return setmetatable(menu, menu.mt)

View File

@ -0,0 +1,412 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat desctop file parser --
-----------------------------------------------------------------------------------------------------------------------
-- Create application menu analyzing .desktop files in given directories
-----------------------------------------------------------------------------------------------------------------------
-- Some code was taken from
------ awful.menubar v3.5.2
------ (c) 2009, 2011-2012 Antonio Terceiro, Alexander Yakushev
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local io = io
local table = table
local ipairs = ipairs
local string = string
local awful = require("awful")
local beautiful = require("beautiful")
local redutil = require("redflat.util")
local gears = require("gears")
-- Initialize tables and vars for module
-----------------------------------------------------------------------------------------------------------------------
local dfparser = {}
local cache = {}
dfparser.terminal = 'uxterm'
local all_icon_folders = { "apps", "actions", "devices", "places", "categories", "status" }
local all_icon_sizes = { '128x128' , '96x96', '72x72', '64x64', '48x48',
'36x36', '32x32', '24x24', '22x22', '16x16', 'scalable' }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
icons = { custom_only = false, scalable_only = false, df_icon = nil, theme = nil },
desktop_file_dirs = { "/usr/share/applications/" },
wm_name = nil,
}
return redutil.table.merge(style, redutil.table.check(beautiful, "service.dfparser") or {})
end
-- Support functions
-----------------------------------------------------------------------------------------------------------------------
-- Cache functions
--------------------------------------------------------------------------------
local function check_cached(req)
for k, v in pairs(cache) do
local eq = #req == #k
for ck, cv in pairs(k) do eq = eq and cv == req[ck] end
if eq then return v end
end
return nil
end
-- Check whether the icon format is supported
--------------------------------------------------------------------------------
local function is_format(icon_file, icon_formats)
for _, f in ipairs(icon_formats) do
if icon_file:match('%.' .. f) then return true end
end
return false
end
-- Find all possible locations of the icon
--------------------------------------------------------------------------------
local function all_icon_path(style)
local icon_theme_paths = {}
-- add user icon theme
if style.theme then
table.insert(icon_theme_paths, style.theme .. '/')
-- TODO also look in parent icon themes, as in freedesktop.org specification
end
-- add fallback theme
if not style.custom_only then table.insert(icon_theme_paths, '/usr/share/icons/hicolor/') end
-- seach only svg icons if need
local current_icon_sizes = style.scalable_only and { 'scalable' } or all_icon_sizes
-- form all avalible icon dirs
local icon_path = {}
for _, icon_theme_directory in ipairs(icon_theme_paths) do
for _, size in ipairs(current_icon_sizes) do
for _, folder in ipairs(all_icon_folders) do
table.insert(icon_path, icon_theme_directory .. size .. "/" .. folder .. '/')
end
end
end
-- lowest priority fallbacks
if not style.custom_only then
table.insert(icon_path, '/usr/share/pixmaps/')
table.insert(icon_path, '/usr/share/icons/')
end
return icon_path
end
-- Lookup an icon in different folders of the filesystem
-- @param icon_file Short or full name of the icon
-- @param style.default_icon Return if no icon was found
-- @param style.icon_theme Full path to custom icon theme
-- @param style.custom_only Seach only custom theme icons, ignore system
-- @param style.scalable_only Seach only svg type icons
-- @return full name of the icon
-----------------------------------------------------------------------------------------------------------------------
function dfparser.lookup_icon(icon_file, style)
style = redutil.table.merge(default_style().icons, style or {})
local df_icon
if style.df_icon and gears.filesystem.file_readable(style.df_icon) then
df_icon = style.df_icon
end
-- No icon name
if not icon_file or icon_file == "" then return df_icon end
-- Handle full path icons
local icon_formats = style.scalable_only and { "svg" } or { "svg", "png", "gif" }
if icon_file:sub(1, 1) == '/' then
if is_format(icon_file, icon_formats) then
return gears.filesystem.file_readable(icon_file) and icon_file or df_icon
else
icon_file = string.match(icon_file, "([%a%d%-]+)%.")
if not icon_file then return df_icon end
end
end
-- Find all possible locations to search
local icon_path = all_icon_path(style)
-- Icon searching
for _, directory in ipairs(icon_path) do
-- check if icon format specified and supported
if is_format(icon_file, icon_formats) and awful.util.file_readable(directory .. icon_file) then
return directory .. icon_file
else
-- check if icon format specified but not supported
if string.match(icon_file, "%.")
and not string.match(icon_file, "org%.gnome%.") -- ignore gnome naming style
and not is_format(icon_file, icon_formats) then
icon_file = string.match(icon_file, "[%a%d%-]+")
end
-- icon is probably specified without path and format,
-- like 'firefox'. Try to add supported extensions to
-- it and see if such file exists.
for _, format in ipairs(icon_formats) do
local possible_file = directory .. icon_file .. "." .. format
if awful.util.file_readable(possible_file) then
return possible_file
end
end
end
end
return df_icon
end
-- Main parsing functions
-----------------------------------------------------------------------------------------------------------------------
-- Parse a .desktop file
-- @param file The .desktop file
-- @param style Arguments for dfparser.lookup_icon
-- @return A table with file entries
--------------------------------------------------------------------------------
local function parse(file, style)
local program = { show = true, file = file }
local desktop_entry = false
-- Parse the .desktop file.
-- We are interested in [Desktop Entry] group only.
for line in io.lines(file) do
if not desktop_entry and line == "[Desktop Entry]" then
desktop_entry = true
else
if line:sub(1, 1) == "[" and line:sub(-1) == "]" then
-- A declaration of new group - stop parsing
break
end
-- Grab the values
for key, value in line:gmatch("(%w+)%s*=%s*(.+)") do
program[key] = value
end
end
end
-- In case [Desktop Entry] was not found
if not desktop_entry then return nil end
-- Don't show program if NoDisplay attribute is false
if program.NoDisplay and string.lower(program.NoDisplay) == "true" then
program.show = false
end
-- Only show the program if there is no OnlyShowIn attribute
-- or if it's equal to given wm name
if program.OnlyShowIn ~= nil and style.wm_name and not program.OnlyShowIn:match(style.wm_name) then
program.show = false
end
-- Look up for a icon.
if program.Icon then
program.icon_path = dfparser.lookup_icon(program.Icon, style.icons)
end
-- Split categories into a table. Categories are written in one
-- line separated by semicolon.
if program.Categories then
program.categories = {}
for category in program.Categories:gmatch('[^;]+') do
table.insert(program.categories, category)
end
end
if program.Exec then
-- Substitute Exec special codes as specified in
-- http://standards.freedesktop.org/desktop-entry-spec/1.1/ar01s06.html
if program.Name == nil then
program.Name = '['.. file:match("([^/]+)%.desktop$") ..']'
end
local cmdline = program.Exec:gsub('%%c', program.Name)
cmdline = cmdline:gsub('%%[fuFU]', '')
cmdline = cmdline:gsub('%%k', program.file)
if program.icon_path then
cmdline = cmdline:gsub('%%i', '--icon ' .. program.icon_path)
else
cmdline = cmdline:gsub('%%i', '')
end
if program.Terminal == "true" then
cmdline = dfparser.terminal .. ' -e ' .. cmdline
end
program.cmdline = cmdline
end
return program
end
-- Parse a directory with .desktop files
-- @param dir The directory
-- @param style Arguments for dfparser.lookup_icon
-- @return A table with all .desktop entries
--------------------------------------------------------------------------------
local function parse_dir(dir, style)
local req = awful.util.table.join({ path = dir }, style.icons)
local programs = {}
local cached = check_cached(req)
if not cached then
local files = redutil.read.output('find '.. dir ..' -maxdepth 1 -name "*.desktop" 2>/dev/null')
for file in string.gmatch(files, "[^\n]+") do
local program = parse(file, style)
if program then table.insert(programs, program) end
end
cache[req] = programs
else
programs = cached
end
return programs
end
-- Create a new application menu
-- @param style.icons Arguments for dfparser.lookup_icon
-- @param style.desktop_file_dirs Table containing all .desktop file directories
-- @return Applications menu table
-----------------------------------------------------------------------------------------------------------------------
function dfparser.menu(style)
style = redutil.table.merge(default_style(), style or {})
-- Categories list
--------------------------------------------------------------------------------
local categories = {
{ app_type = "AudioVideo", name = "Multimedia", icon_name = "applications-multimedia" },
{ app_type = "Development", name = "Development", icon_name = "applications-development" },
{ app_type = "Education", name = "Education", icon_name = "applications-science" },
{ app_type = "Game", name = "Games", icon_name = "applications-games" },
{ app_type = "Graphics", name = "Graphics", icon_name = "applications-graphics" },
{ app_type = "Office", name = "Office", icon_name = "applications-office" },
{ app_type = "Network", name = "Internet", icon_name = "applications-internet" },
{ app_type = "Settings", name = "Settings", icon_name = "applications-utilities" },
{ app_type = "System", name = "System Tools", icon_name = "applications-system" },
{ app_type = "Utility", name = "Accessories", icon_name = "applications-accessories" }
}
-- Find icons for categories
--------------------------------------------------------------------------------
for _, v in ipairs(categories) do
v.icon = dfparser.lookup_icon(v.icon_name, style)
end
-- Find all visible menu items
--------------------------------------------------------------------------------
local prog_list = {}
for _, path in ipairs(style.desktop_file_dirs) do
local programs = parse_dir(path, style)
for _, prog in ipairs(programs) do
if prog.show and prog.Name and prog.cmdline then
table.insert(prog_list, prog)
end
end
end
-- Sort menu items by category and create submenu
--------------------------------------------------------------------------------
local appmenu = {}
for _, menu_category in ipairs(categories) do
local catmenu = {}
for i = #prog_list, 1, -1 do
if prog_list[i].categories then
for _, item_category in ipairs(prog_list[i].categories) do
if item_category == menu_category.app_type then
table.insert(catmenu, { prog_list[i].Name, prog_list[i].cmdline, prog_list[i].icon_path })
table.remove(prog_list, i)
end
end
end
end
if #catmenu > 0 then table.insert(appmenu, { menu_category.name, catmenu, menu_category.icon }) end
end
-- Collect all items without category to "Other" submenu
--------------------------------------------------------------------------------
if #prog_list > 0 then
local catmenu = {}
for _, prog in ipairs(prog_list) do
table.insert(catmenu, { prog.Name, prog.cmdline, prog.icon_path })
end
table.insert(appmenu, { "Other", catmenu, dfparser.lookup_icon("applications-other") })
end
return appmenu
end
-- Get list of icons linked with process name
-- @param style.icons Arguments for dfparser.lookup_icon
-- @param style.desktop_file_dirs Table containing all .desktop file directories
-- @return Icon list
-----------------------------------------------------------------------------------------------------------------------
function dfparser.icon_list(style)
style = redutil.table.merge(default_style(), style or {})
local list = {}
for _, path in ipairs(style.desktop_file_dirs) do
local programs = parse_dir(path, style)
for _, prog in ipairs(programs) do
if prog.Icon and prog.Exec then
local key = string.match(prog.Exec, "[%a%d%.%-/]+")
if string.find(key, "/") then key = string.match(key, "[%a%d%.%-]+$") end
list[key] = prog.icon_path
end
end
end
return list
end
-- Generate table with avaliable programs
-- without sorting by categories
-- @param style.icons Arguments for dfparser.lookup_icon
-- @param style.desktop_file_dirs Table containing all .desktop file directories
-----------------------------------------------------------------------------------------------------------------------
function dfparser.program_list(style)
style = redutil.table.merge(default_style(), style or {})
local prog_list = {}
for _, path in ipairs(style.desktop_file_dirs) do
local programs = parse_dir(path, style)
for _, prog in ipairs(programs) do
if prog.show and prog.Name and prog.cmdline then
table.insert(prog_list, prog)
end
end
end
return prog_list
end
-- End
-----------------------------------------------------------------------------------------------------------------------
return dfparser

View File

@ -0,0 +1,10 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat library --
-----------------------------------------------------------------------------------------------------------------------
local wrequire = require("redflat.util").wrequire
local setmetatable = setmetatable
local lib = { _NAME = "redflat.service" }
return setmetatable(lib, { __index = wrequire })

View File

@ -0,0 +1,355 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat focus switch util --
-----------------------------------------------------------------------------------------------------------------------
-- Visual clinet managment helper
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local math = math
local awful = require("awful")
local wibox = require("wibox")
local color = require("gears.color")
local beautiful = require("beautiful")
local timer = require("gears.timer")
local redflat = require("redflat")
local redutil = require("redflat.util")
local redtip = require("redflat.float.hotkeys")
local rednotify = require("redflat.float.notify")
-- Initialize tables and vars for module
-----------------------------------------------------------------------------------------------------------------------
local navigator = { action = {}, data = {}, active = false }
navigator.ignored = { "dock", "splash", "desktop" }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
border_width = 2,
marksize = { width = 200, height = 100, r = 20 },
gradstep = 100,
linegap = 35,
timeout = 1,
notify = {},
keytip = { base = { geometry = { width = 600 }, exit = true } },
titlefont = { font = "Sans", size = 28, face = 1, slant = 0 },
num = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "F1", "F3", "F4", "F5" },
font = { font = "Sans", size = 22, face = 1, slant = 0 },
color = { border = "#575757", wibox = "#00000000", bg1 = "#57575740", bg2 = "#57575720",
fbg1 = "#b1222b40", fbg2 = "#b1222b20", mark = "#575757", text = "#202020",
hbg1 = "#32882d40", hbg2 = "#32882d20" },
shape = nil
}
return redutil.table.merge(style, redutil.table.check(beautiful, "service.navigator") or {})
end
-- Support functions
-----------------------------------------------------------------------------------------------------------------------
-- Check geometry intersection
--------------------------------------------------------------------------------
local function is_intersect(a, b)
return (b.x < a.x + a.width and b.x + b.width > a.x and b.y < a.y + a.height and b.y + b.height > a.y)
end
-- Window painting
--------------------------------------------------------------------------------
function navigator.make_paint(c)
-- Initialize vars
------------------------------------------------------------
local style = navigator.style
local widg = wibox.widget.base.make_widget()
widg._data = {
client = c,
alert = false,
}
-- User functions
------------------------------------------------------------
function widg:set_client(client_)
if widg._data.client ~= client_ then
widg._data.client = client_
self:emit_signal("widget::redraw_needed")
end
end
function widg:set_alert(value)
if widg._data.alert ~= value then
widg._data.alert = value
self:emit_signal("widget::redraw_needed")
end
end
-- Fit
------------------------------------------------------------
function widg:fit(_, width, height)
return width, height
end
-- Draw
------------------------------------------------------------
function widg:draw(_, cr, width, height)
if not widg._data.client then return end
-- background
local bg1, bg2
local num = math.ceil((width + height) / style.gradstep)
if widg._data.alert then
bg1, bg2 = style.color.hbg1, style.color.hbg2
else
local is_focused = widg._data.client == client.focus
bg1 = is_focused and style.color.fbg1 or style.color.bg1
bg2 = is_focused and style.color.fbg2 or style.color.bg2
end
for i = 1, num do
local cc = i % 2 == 1 and bg1 or bg2
local l = i * style.gradstep
cr:set_source(color(cc))
cr:move_to(0, (i - 1) * style.gradstep)
cr:rel_line_to(0, style.gradstep)
cr:rel_line_to(l, - l)
cr:rel_line_to(- style.gradstep, 0)
cr:close_path()
cr:fill()
end
-- rounded rectangle on center
local r = style.marksize.r
local w, h = style.marksize.width - 2 * r, style.marksize.height - 2 * r
cr:set_source(color(style.color.mark))
cr:move_to((width - w) / 2 - r, (height - h) / 2)
cr:rel_curve_to(0, -r, 0, -r, r, -r)
cr:rel_line_to(w, 0)
cr:rel_curve_to(r, 0, r, 0, r, r)
cr:rel_line_to(0, h)
cr:rel_curve_to(0, r, 0, r, -r, r)
cr:rel_line_to(-w, 0)
cr:rel_curve_to(-r, 0, -r, 0, -r, -r)
cr:close_path()
cr:fill()
-- label
local index = navigator.style.num[awful.util.table.hasitem(navigator.cls, widg._data.client)]
local g = redutil.client.fullgeometry(widg._data.client)
cr:set_source(color(style.color.text))
redutil.cairo.set_font(cr, style.titlefont)
redutil.cairo.textcentre.full(cr, { width/2, height/2 - style.linegap / 2 }, index)
redutil.cairo.set_font(cr, style.font)
redutil.cairo.textcentre.full(cr, { width/2, height/2 + style.linegap / 2 }, g.width .. " x " .. g.height)
end
------------------------------------------------------------
return widg
end
-- Construct wibox
--------------------------------------------------------------------------------
function navigator.make_decor(c)
local object = {}
local style = navigator.style
-- Create wibox
------------------------------------------------------------
object.wibox = wibox({
ontop = true,
bg = style.color.wibox,
border_width = style.border_width,
border_color = style.color.border,
shape = style.shape
})
object.client = c
object.widget = navigator.make_paint(c)
object.wibox:set_widget(object.widget)
-- User functions
------------------------------------------------------------
object.update = {
focus = function() object.widget:emit_signal("widget::redraw_needed") end,
close = function() navigator:restart() end,
geometry = function() redutil.client.fullgeometry(object.wibox, redutil.client.fullgeometry(object.client)) end
}
function object:set_client(client_)
object.client = client_
object.widget:set_client(client_)
redutil.client.fullgeometry(object.wibox, redutil.client.fullgeometry(object.client))
object.client:connect_signal("focus", object.update.focus)
object.client:connect_signal("unfocus", object.update.focus)
object.client:connect_signal("property::geometry", object.update.geometry)
object.client:connect_signal("unmanage", object.update.close)
end
function object:clear(no_hide)
object.client:disconnect_signal("focus", object.update.focus)
object.client:disconnect_signal("unfocus", object.update.focus)
object.client:disconnect_signal("property::geometry", object.update.geometry)
object.client:disconnect_signal("unmanage", object.update.close)
object.widget:set_client()
if not no_hide then object.wibox.visible = false end
end
------------------------------------------------------------
object:set_client(c)
return object
end
-- Main functions
-----------------------------------------------------------------------------------------------------------------------
function navigator:init()
-- Style
------------------------------------------------------------
self.style = default_style()
-- Hilight area
------------------------------------------------------------
self.hilight = {}
-- timer
self.hilight.hidetimer = timer({ timeout = self.style.timeout })
self.hilight.hidetimer:connect_signal("timeout", function() self.hilight.hide() end)
-- show/hide
function self.hilight.show(g)
for i, c in ipairs(self.cls) do
self.data[i].widget:set_alert(is_intersect(g, c:geometry()))
end
self.hilight.hidetimer:again()
end
function self.hilight.hide()
for i, _ in ipairs(self.cls) do self.data[i].widget:set_alert(false) end
end
-- close the navigator on tag switch
tag.connect_signal('property::selected',
function()
if navigator.active then self:close() end
end
)
-- update navigator if new client spawns
client.connect_signal('manage',
function()
if navigator.active then self:restart() end
end
)
-- update navigator if a client gets minimized or restored
client.connect_signal('property::minimized',
function()
if navigator.active then self:restart() end
end
)
end
function navigator:run()
if not self.style then self:init() end
-- check clients
local s = mouse.screen
self.cls = awful.client.tiled(s)
if #self.cls == 0 or
not client.focus or
client.focus.fullscreen or
awful.util.table.hasitem(navigator.ignored, client.focus.type)
then
return
end
-- check handler
local l = awful.layout.get(client.focus.screen)
local handler = l.key_handler or redflat.layout.common.handler[l]
if not handler then
rednotify:show(redutil.table.merge({ text = "Layout not supported" }, self.style.notify))
return
end
-- layout setup if needed
if l.startup then l.startup() end
local tip = l.tip or redflat.layout.common.tips[l]
-- activate navition widgets
for i, c in ipairs(self.cls) do
if not self.data[i] then
self.data[i] = self.make_decor(c)
else
self.data[i]:set_client(c)
end
self.data[i].wibox.visible = true
end
-- run key handler
self.grabber_settled = handler
awful.keygrabber.run(self.grabber_settled)
-- set keys tip
self.tip_settled = tip
if tip then
local tip_style = self.style.keytip[awful.layout.getname(l)] or self.style.keytip.base
redtip:set_pack(
"Layout " .. l.name, tip, tip_style.column, tip_style.geometry,
self.style.keytip.base.exit and function() redflat.layout.common.action.exit() end -- fix this?
)
end
navigator.active = true
end
function navigator:close()
for i, _ in ipairs(self.cls) do
self.data[i]:clear()
end
awful.keygrabber.stop(self.grabber_settled)
if self.tip_settled then redtip:remove_pack() end
local l = client.focus and awful.layout.get(client.focus.screen)
if l and l.cleanup then l.cleanup() end
self.cls = {}
navigator.active = false
end
function navigator:restart()
-- update decoration
for i, _ in ipairs(self.cls) do self.data[i]:clear(true) end
local newcls = awful.client.tiled(mouse.screen)
for i = 1, math.max(#self.cls, #newcls) do
if newcls[i] then
if not self.data[i] then
self.data[i] = self.make_decor(newcls[i])
else
self.data[i]:set_client(newcls[i])
end
self.data[i].wibox.visible = true
else
self.data[i].wibox.visible = false
end
end
self.cls = newcls
end
-- End
-----------------------------------------------------------------------------------------------------------------------
return navigator

View File

@ -0,0 +1,44 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat startup check --
-----------------------------------------------------------------------------------------------------------------------
-- Save exit reason to file
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local io = io
local redutil = require("redflat.util")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local startup = { locked = false }
startup.path = "/tmp/awesome-exit-reason"
--startup.bin = "awesome-client"
local REASON = { RESTART = "restart", EXIT = "exit" }
-- Stamp functions
-----------------------------------------------------------------------------------------------------------------------
-- save restart reason
function startup.stamp(reason_restart)
local file = io.open(startup.path, "w")
file:write(reason_restart)
file:close()
end
function startup:activate()
-- check if it is first start
local reason = redutil.read.file(startup.path)
self.is_startup = (not reason or reason == REASON.EXIT) and not self.locked
-- save reason on exit
awesome.connect_signal("exit",
function(is_restart) startup.stamp(is_restart and REASON.RESTART or REASON.EXIT) end
)
end
-----------------------------------------------------------------------------------------------------------------------
return startup

View File

@ -0,0 +1,753 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat system --
-----------------------------------------------------------------------------------------------------------------------
-- System monitoring functions collected here
-----------------------------------------------------------------------------------------------------------------------
-- Some code was taken from
------ vicious module
------ (c) 2010, 2011 Adrian C. <anrxc@sysphere.org>
------ (c) 2009, Lucas de Vries <lucas@glacicle.com>
------ (c) 2011, Jörg T. <jthalheim@gmail.com>
------ (c) 2011, Adrian C. <anrxc@sysphere.org>
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local tonumber = tonumber
local io = io
local os = os
local string = string
local math = math
local timer = require("gears.timer")
local awful = require("awful")
local redutil = require("redflat.util")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local system = { thermal = {}, dformatted = {}, pformatted = {} }
-- Async settlers generator
-----------------------------------------------------------------------------------------------------------------------
function system.simple_async(command, pattern)
return function(setup)
awful.spawn.easy_async_with_shell(command,
function(output)
local value = tonumber(string.match(output, pattern))
setup(value and { value } or { 0 })
end
)
end
end
-- Disk usage
-----------------------------------------------------------------------------------------------------------------------
function system.fs_info(args)
local fs_info = {}
args = args or "/"
-- Get data from df
------------------------------------------------------------
local line = redutil.read.output("LC_ALL=C df -kP " .. args .. " | tail -1")
-- Parse data
------------------------------------------------------------
fs_info.size = string.match(line, "^.-[%s]([%d]+)")
fs_info.mount = string.match(line, "%%[%s]([%p%w]+)")
fs_info.used, fs_info.avail, fs_info.use_p = string.match(line, "([%d]+)[%D]+([%d]+)[%D]+([%d]+)%%")
-- Format output special for redflat desktop widget
------------------------------------------------------------
return { tonumber(fs_info.use_p) or 0, tonumber(fs_info.used) or 0}
end
-- Qemu image check
-----------------------------------------------------------------------------------------------------------------------
local function q_format(size, k)
if not size or not k then return 0 end
return k == "K" and tonumber(size) or k == "M" and size * 1024 or k == "G" and size * 1024^2 or 0
end
function system.qemu_image_size(args)
local img_info = {}
-- Get data from qemu-ima
------------------------------------------------------------
local line = redutil.read.output("LC_ALL=C qemu-img info " .. args)
-- Parse data
------------------------------------------------------------
local size, k = string.match(line, "disk%ssize:%s([%.%d]+)%s(%w)")
img_info.size = q_format(size, k)
local vsize, vk = string.match(line, "virtual%ssize:%s([%.%d]+)%s(%w)")
img_info.virtual_size = q_format(vsize, vk)
img_info.use_p = img_info.virtual_size > 0 and math.floor(img_info.size / img_info.virtual_size * 100) or 0
-- Format output special for redflat desktop widget
------------------------------------------------------------
return { img_info.use_p, img_info.size, off = img_info.size == 0 }
end
-- Traffic check with vnstat (async)
-----------------------------------------------------------------------------------------------------------------------
local function vnstat_format(value, unit)
if not value or not unit then return 0 end
local v = value:gsub(',', '.')
return unit == "B" and tonumber(v)
or unit == "KiB" and v * 1024
or unit == "MiB" and v * 1024^2
or unit == "GiB" and v * 1024^3
end
function system.vnstat_check(args)
local command = string.format("vnstat %s | tail -n 3 | head -n 1", args)
return function(setup)
awful.spawn.easy_async_with_shell(command,
function(output)
local x, u = string.match(
output, "%s+%d+,%d+%s%w+%s+%|%s+%d+,%d+%s%w+%s+%|%s+(%d+,%d+)%s(%w+)%s+%|%s+.+"
)
local total = vnstat_format(x, u)
setup({ total })
end
)
end
end
-- Get network speed
-----------------------------------------------------------------------------------------------------------------------
function system.net_speed(interface, storage)
local up, down = 0, 0
-- Get network info
--------------------------------------------------------------------------------
for line in io.lines("/proc/net/dev") do
-- Match wmaster0 as well as rt0 (multiple leading spaces)
local name = string.match(line, "^[%s]?[%s]?[%s]?[%s]?([%w]+):")
-- Calculate speed for given interface
------------------------------------------------------------
if name == interface then
-- received bytes, first value after the name
local recv = tonumber(string.match(line, ":[%s]*([%d]+)"))
-- transmited bytes, 7 fields from end of the line
local send = tonumber(string.match(line, "([%d]+)%s+%d+%s+%d+%s+%d+%s+%d+%s+%d+%s+%d+%s+%d$"))
local now = os.time()
if not storage[interface] then
-- default values on the first run
storage[interface] = { recv = 0, send = 0 }
else
-- net stats are absolute, substract our last reading
local interval = now - storage[interface].time
if interval <= 0 then interval = 1 end
down = (recv - storage[interface].recv) / interval
up = (send - storage[interface].send) / interval
end
-- store totals
storage[interface].time = now
storage[interface].recv = recv
storage[interface].send = send
end
end
--------------------------------------------------------------------------------
return { up, down }
end
-- Get disk speed
-----------------------------------------------------------------------------------------------------------------------
function system.disk_speed(disk, storage)
local up, down = 0, 0
-- Get i/o info
--------------------------------------------------------------------------------
for line in io.lines("/proc/diskstats") do
-- parse info
-- linux kernel documentation: Documentation/iostats.txt
local device, read, write = string.match(line, "([^%s]+) %d+ %d+ (%d+) %d+ %d+ %d+ (%d+)")
-- Calculate i/o for given device
------------------------------------------------------------
if device == disk then
local now = os.time()
local stats = { read, write }
if not storage[disk] then
-- default values on the first run
storage[disk] = { stats = stats }
else
-- check for overflows and counter resets (> 2^32)
if stats[1] < storage[disk].stats[1] or stats[2] < storage[disk].stats[2] then
storage[disk].stats[1], storage[disk].stats[2] = stats[1], stats[2]
end
-- diskstats are absolute, substract our last reading
-- * divide by timediff because we don't know the timer value
local interval = now - storage[disk].time
if interval <= 0 then interval = 1 end
up = (stats[1] - storage[disk].stats[1]) / interval
down = (stats[2] - storage[disk].stats[2]) / interval
end
-- store totals
storage[disk].time = now
storage[disk].stats = stats
end
end
--------------------------------------------------------------------------------
return { up, down }
end
-- Get MEM info
-----------------------------------------------------------------------------------------------------------------------
function system.memory_info()
local mem = { buf = {}, swp = {} }
-- Get MEM info
------------------------------------------------------------
for line in io.lines("/proc/meminfo") do
for k, v in string.gmatch(line, "([%a]+):[%s]+([%d]+).+") do
if k == "MemTotal" then mem.total = math.floor(v/1024)
elseif k == "MemFree" then mem.buf.f = math.floor(v/1024)
elseif k == "Buffers" then mem.buf.b = math.floor(v/1024)
elseif k == "Cached" then mem.buf.c = math.floor(v/1024)
elseif k == "SwapTotal" then mem.swp.t = math.floor(v/1024)
elseif k == "SwapFree" then mem.swp.f = math.floor(v/1024)
end
end
end
-- Calculate memory percentage
------------------------------------------------------------
mem.free = mem.buf.f + mem.buf.b + mem.buf.c
mem.inuse = mem.total - mem.free
mem.bcuse = mem.total - mem.buf.f
mem.usep = math.floor(mem.inuse / mem.total * 100)
-- calculate swap percentage
mem.swp.inuse = mem.swp.t - mem.swp.f
mem.swp.usep = mem.swp.t > 0 and math.floor(mem.swp.inuse / mem.swp.t * 100) or 0
------------------------------------------------------------
return mem
end
-- Get cpu usage info
-----------------------------------------------------------------------------------------------------------------------
--local storage = { cpu_total = {}, cpu_active = {} } -- storage structure
function system.cpu_usage(storage)
local cpu_lines = {}
local cpu_usage = {}
local diff_time_total
-- Get CPU stats
------------------------------------------------------------
for line in io.lines("/proc/stat") do
if string.sub(line, 1, 3) == "cpu" then
local digits_in_line = {}
for i in string.gmatch(line, "[%s]+([^%s]+)") do
table.insert(digits_in_line, i)
end
table.insert(cpu_lines, digits_in_line)
end
end
-- Calculate usage
------------------------------------------------------------
for i, line in ipairs(cpu_lines) do
-- calculate totals
local total_new = 0
for _, value in ipairs(line) do total_new = total_new + value end
local active_new = total_new - (line[4] + line[5])
-- calculate percentage
local diff_total = total_new - (storage.cpu_total[i] or 0)
local diff_active = active_new - (storage.cpu_active[i] or 0)
if i == 1 then diff_time_total = diff_total end
if diff_total == 0 then diff_total = 1E-6 end
cpu_usage[i] = math.floor((diff_active / diff_total) * 100)
-- store totals
storage.cpu_total[i] = total_new
storage.cpu_active[i] = active_new
end
-- Format output special for redflat widgets and other system functions
------------------------------------------------------------
local total_usage = cpu_usage[1]
local core_usage = awful.util.table.clone(cpu_usage)
table.remove(core_usage, 1)
return { total = total_usage, core = core_usage, diff = diff_time_total }
end
-- Get battery level and charging status
-----------------------------------------------------------------------------------------------------------------------
function system.battery(batname)
if not batname then return end
-- Initialzie vars
--------------------------------------------------------------------------------
local battery = {}
local time = "N/A"
local battery_state = {
["Full\n"] = "",
["Unknown\n"] = "",
["Charged\n"] = "",
["Charging\n"] = "+",
["Discharging\n"] = "-"
}
local files = {
"present", "status", "charge_now",
"charge_full", "energy_now", "energy_full",
"current_now", "power_now"
}
-- Read info
--------------------------------------------------------------------------------
for _, v in pairs(files) do
battery[v] = redutil.read.file("/sys/class/power_supply/" .. batname .. "/" .. v)
end
-- Check if the battery is present
------------------------------------------------------------
if battery.present ~= "1\n" then
return { battery_state["Unknown\n"], 0, "N/A" }
end
-- Get state information
------------------------------------------------------------
local state = battery_state[battery.status] or battery_state["Unknown\n"]
local remaining, capacity
-- Get capacity information
if battery.charge_now then remaining, capacity = battery.charge_now, battery.charge_full
elseif battery.energy_now then remaining, capacity = battery.energy_now, battery.energy_full
else return {battery_state["Unknown\n"], 0, "N/A"}
end
-- Calculate percentage (but work around broken BAT/ACPI implementations)
------------------------------------------------------------
local percent = math.min(math.floor(remaining / capacity * 100), 100)
-- Get charge information
------------------------------------------------------------
local rate
if battery.current_now then rate = tonumber(battery.current_now)
elseif battery.power_now then rate = tonumber(battery.power_now)
else return {state, percent, "N/A"}
end
-- Calculate remaining (charging or discharging) time
------------------------------------------------------------
if rate ~= nil and rate ~= 0 then
local timeleft
if state == "+" then timeleft = (tonumber(capacity) - tonumber(remaining)) / tonumber(rate)
elseif state == "-" then timeleft = tonumber(remaining) / tonumber(rate)
else return {state, percent, time}
end
-- calculate time
local hoursleft = math.floor(timeleft)
local minutesleft = math.floor((timeleft - hoursleft) * 60 )
time = string.format("%02d:%02d", hoursleft, minutesleft)
end
--------------------------------------------------------------------------------
return { state, percent, time }
end
-- Temperature measure
-----------------------------------------------------------------------------------------------------------------------
-- Using lm-sensors
------------------------------------------------------------
system.lmsensors = { storage = {}, patterns = {}, delay = 1, time = 0 }
function system.lmsensors:update(output)
for name, pat in pairs(self.patterns) do
local value = string.match(output, pat.match)
if value and pat.posthook then value = pat.posthook(value) end
value = tonumber(value)
self.storage[name] = value and { value } or { 0 }
end
self.time = os.time()
end
function system.lmsensors:start(timeout)
if self.timer then return end
self.timer = timer({ timeout = timeout })
self.timer:connect_signal("timeout", function()
awful.spawn.easy_async("sensors", function(output) system.lmsensors:update(output) end)
end)
self.timer:start()
self.timer:emit_signal("timeout")
end
function system.lmsensors:soft_start(timeout, shift)
if self.timer then return end
timer({
timeout = timeout - (shift or 1),
autostart = true,
single_shot = true,
callback = function() self:start(timeout) end
})
end
function system.lmsensors.get(name)
if os.time() - system.lmsensors.time > system.lmsensors.delay then
local output = redutil.read.output("sensors")
system.lmsensors:update(output)
end
return system.lmsensors.storage[name] or { 0 }
end
-- Legacy
------------------------------------------------------------
--function system.thermal.sensors(args)
-- local args = args or "'Physical id 0'"
-- local output = redutil.read.output("sensors | grep " .. args)
--
-- local temp = string.match(output, "%+(%d+%.%d)°[CF]")
--
-- return temp and { math.floor(tonumber(temp)) } or { 0 }
--end
--
--local sensors_store
--
--function system.thermal.sensors_core(args)
-- args = args or {}
-- local index = args.index or 0
--
-- if args.main then sensors_store = redutil.read.output("sensors | grep Core") end
-- local line = string.match(sensors_store, "Core " .. index .."(.-)\r?\n")
--
-- if not line then return { 0 } end
--
-- local temp = string.match(line, "%+(%d+%.%d)°[CF]")
-- return temp and { math.floor(tonumber(temp)) } or { 0 }
--end
-- Using hddtemp
------------------------------------------------------------
function system.thermal.hddtemp(args)
args = args or {}
local port = args.port or "7634"
local disk = args.disk or "/dev/sda"
local output = redutil.read.output("echo | curl --connect-timeout 1 -fsm 3 telnet://127.0.0.1:" .. port)
for mnt, _, temp, _ in output:gmatch("|(.-)|(.-)|(.-)|(.-)|") do
if mnt == disk then
return temp and { tonumber(temp) }
end
end
return { 0 }
end
-- Using nvidia-settings on sysmem with optimus (bumblebee)
-- Async
------------------------------------------------------------
function system.thermal.nvoptimus(setup)
local nvidia_on = string.find(redutil.read.output("cat /proc/acpi/bbswitch"), "ON")
if not nvidia_on then
setup({ 0, off = true })
else
awful.spawn.easy_async_with_shell("optirun -b none nvidia-settings -c :8 -q gpucoretemp -t | tail -1",
function(output)
local value = tonumber(string.match(output, "[^\n]+"))
setup({ value or 0, off = false })
end
)
end
end
-- Direct call of nvidia-smi
------------------------------------------------------------
function system.thermal.nvsmi()
local temp = string.match(
redutil.read.output("nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader"), "%d%d"
)
-- checks that local temp is not null then returns the convert string to number or if fails returns null
return temp and { tonumber(temp) } or { 0 }
end
-- Using nvidia-smi on sysmem with optimus (nvidia-prime)
------------------------------------------------------------
function system.thermal.nvprime()
local temp = 0
local nvidia_on = string.find(redutil.read.output("prime-select query"), "nvidia")
if nvidia_on ~= nil then
-- reuse function nvsmi
temp = system.thermal.nvsmi()[1]
end
return { temp, off = nvidia_on == nil }
end
-- Get info from transmission-remote client
-- This function adapted special for async reading
-----------------------------------------------------------------------------------------------------------------------
system.transmission = {}
-- Check if transmission client running
--------------------------------------------------------------------------------
function system.transmission.is_running(args)
local t_client = args or "transmission-gtk"
return redutil.read.output("pidof -x " .. t_client) ~= ""
end
-- Function for torrents sorting (downloading and paused first)
--------------------------------------------------------------------------------
function system.transmission.sort_torrent(a, b)
return a.status == "Downloading" and b.status ~= "Downloading"
or a.status == "Stopped" and b.status ~= "Stopped" and b.status ~= "Downloading"
end
-- Function to parse 'transmission-remote -l' output
--------------------------------------------------------------------------------
function system.transmission.parse(output, show_active_only)
-- Initialize vars
------------------------------------------------------------
local torrent = {
seed = { num = 0, speed = 0 },
dnld = { num = 0, speed = 0 },
list = {},
}
-- Find state and progress for every torrent
-- and total upload and downoad speed
------------------------------------------------------------
--local status_pos = string.find(output, "Status")
-- assuming "Up & Down" and "Downloading" is the same thing
output = string.gsub(output, "Up & Down", "Downloading")
-- parse every line
for line in string.gmatch(output, "[^\r\n]+") do
if string.sub(line, 1, 3) == "Sum" then
-- get total speed
local seed, dnld = string.match(line, "Sum:%s+[%d%.]+%s+%a+%s+([%d%.]+)%s+([%d%.]+)")
seed, dnld = tonumber(seed), tonumber(dnld)
if seed and dnld then
torrent.seed.speed, torrent.dnld.speed = seed, dnld
end
else
-- get torrent info
local prog, status, name = string.match(
line,
"%s+%d+%s+(%d+)%%%s+[%d%.]+%s%a+%s+.+%s+[%d%.]+%s+[%d%.]+%s+[%d%.]+%s+(%a+)%s+(.+)"
)
if prog and status then
-- if active only is selected then filter
if not show_active_only or (status == "Downloading" or status == "Seeding") then
table.insert(torrent.list, { prog = prog, status = status, name = name })
end
if status == "Seeding" then
torrent.seed.num = torrent.seed.num + 1
elseif status == "Downloading" then
torrent.dnld.num = torrent.dnld.num + 1
end
end
end
end
-- Sort torrents
------------------------------------------------------------
-- do not need to sort active as transmission-remote automatically sorts
if not show_active_only then
table.sort(torrent.list, system.transmission.sort_torrent)
end
-- Format output special for redflat desktop widget
------------------------------------------------------------
local sorted_prog = {}
for _, t in ipairs(torrent.list) do
table.insert(sorted_prog, { value = t.prog, text = string.format("%d%% %s", t.prog, t.name) })
end
return {
bars = sorted_prog,
lines = { { torrent.seed.speed, torrent.seed.num }, { torrent.dnld.speed, torrent.dnld.num } },
alert = false
}
end
-- Async transmission meter function
--------------------------------------------------------------------------------
function system.transmission.info(setup, args)
local command = args.command or "transmission-remote localhost -l"
awful.spawn.easy_async(command, function(output)
-- rather than check if an instance of transmission is running locally, check if there is actually any output
-- zero torrents or no program equates to same result
if string.len(output) > 0 then
local state = system.transmission.parse(output, args.show_active_only)
if args.speed_only then
state.lines[1][2] = state.lines[1][1]
state.lines[2][2] = state.lines[2][1]
end
setup(state)
else
setup({ bars = {}, lines = { { 0, 0 }, { 0, 0 } }, alert = true })
end
end)
end
-- Get processes list and cpu and memory usage for every process
-- !!! Fixes is needed !!!
-----------------------------------------------------------------------------------------------------------------------
local proc_storage = {}
function system.proc_info(cpu_storage)
local process = {}
local mem_page_size = 4
-- get processes list with ps utility
-- !!! TODO: get processes list from fs directly !!!
local output = redutil.read.output("ps -eo pid | tail -n +2")
-- get total cpu time diff from previous call
local cpu_diff = system.cpu_usage(cpu_storage).diff
-- handle every line in ps output
for line in string.gmatch(output, "[^\n]+") do
local pid = tonumber(line)
-- try to get info from /proc
local stat = redutil.read.file("/proc/" .. pid .. "/stat")
-- if process with given pid exist in /proc
if stat then
-- get process name
local name = string.match(stat, ".+%((.+)%).+")
local proc_stat = { name }
-- remove process name from stat data to simplify following parsing
stat = stat:gsub("%s%(.+%)", "", 1)
-- the rest of 'stat' data can be splitted by whitespaces
-- first chunk is pid so just skip it
for m in string.gmatch(stat, "[%s]+([^%s]+)") do
table.insert(proc_stat, m)
end
-- get memory usage (RSS)
-- !!! RSS is a very crude approximation for memory usage !!!
-- !!! TODO: find a more accurate method for real memory usage calculation !!!
local mem = proc_stat[23] * mem_page_size
-- calculate cpu usage for process
local proc_time = proc_stat[13] + proc_stat[14]
local pcpu = (proc_time - (proc_storage[pid] or 0)) / cpu_diff
-- save current cpu time for future
proc_storage[pid] = proc_time
-- save results
table.insert(process, { pid = pid, name = name, mem = mem, pcpu = pcpu })
end
end
return process
end
-- Output format functions
-----------------------------------------------------------------------------------------------------------------------
-- CPU and memory usage formatted special for desktop widget
--------------------------------------------------------------------------------
function system.dformatted.cpumem(storage)
local mem = system.memory_info()
local cores = {}
for i, v in ipairs(system.cpu_usage(storage).core) do
table.insert(cores, { value = v, text = string.format("CORE%d %s%%", i - 1, v) })
end
return {
bars = cores,
lines = { { mem.usep, mem.inuse }, { mem.swp.usep, mem.swp.inuse } }
}
end
-- CPU usage formatted special for panel widget
--------------------------------------------------------------------------------
function system.pformatted.cpu(crit)
crit = crit or 75
local storage = { cpu_total = {}, cpu_active = {} }
return function()
local usage = system.cpu_usage(storage).total
return {
value = usage / 100,
text = usage .. "%",
alert = usage > crit
}
end
end
-- Memory usage formatted special for panel widget
--------------------------------------------------------------------------------
function system.pformatted.mem(crit)
crit = crit or 75
return function()
local usage = system.memory_info().usep
return {
value = usage / 100,
text = usage .. "%",
alert = usage > crit
}
end
end
-- Battery state formatted special for panel widget
--------------------------------------------------------------------------------
function system.pformatted.bat(crit)
crit = crit or 15
return function(arg)
local state = system.battery(arg)
return {
value = state[2] / 100,
text = state[1] .. " " .. state[2] .. "% " .. state[3],
alert = state[2] < crit
}
end
end
-- End
-----------------------------------------------------------------------------------------------------------------------
return system

View File

@ -0,0 +1,442 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat titlebar --
-----------------------------------------------------------------------------------------------------------------------
-- model titlebar with two view: light and full
-- Only simple indicators avaliable, no buttons
-----------------------------------------------------------------------------------------------------------------------
-- Some code was taken from
------ awful.titlebar v3.5.2
------ (c) 2012 Uli Schlachter
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local error = error
local table = table
local unpack = unpack or table.unpack
local awful = require("awful")
local drawable = require("wibox.drawable")
local color = require("gears.color")
local wibox = require("wibox")
local redutil = require("redflat.util")
local svgbox = require("redflat.gauge.svgbox")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local titlebar = { mt = {}, widget = {}, _index = 1, _num = 1 }
titlebar.list = setmetatable({}, { __mode = 'k' })
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local default_style = {
size = 8,
position = "top",
font = "Sans 12 bold",
border_margin = { 0, 0, 0, 4 },
color = { main = "#b1222b", wibox = "#202020", gray = "#575757",
text = "#aaaaaa", icon = "#a0a0a0", urgent = "#32882d" }
}
local default_mark_style = {
size = 20,
angle = 0,
color = default_style.color
}
local default_button_style = {
list = { unknown = redutil.base.placeholder({ txt = "X" }) },
color = default_style.color
}
local positions = { "left", "right", "top", "bottom" }
-- Support functions
-----------------------------------------------------------------------------------------------------------------------
-- Get titlebar function
------------------------------------------------------------
local function get_titlebar_function(c, position)
if position == "left" then return c.titlebar_left
elseif position == "right" then return c.titlebar_right
elseif position == "top" then return c.titlebar_top
elseif position == "bottom" then return c.titlebar_bottom
else
error("Invalid titlebar position '" .. position .. "'")
end
end
-- Get titlebar model
------------------------------------------------------------
function titlebar.get_model(c, position)
position = position or "top"
return titlebar.list[c] and titlebar.list[c][position] or nil
end
-- Get titlebar client list
------------------------------------------------------------
function titlebar.get_clients()
local cl = {}
for c, _ in pairs(titlebar.list) do table.insert(cl, c) end
return cl
end
-- Build client titlebar
-----------------------------------------------------------------------------------------------------------------------
function titlebar.new(c, style)
if not titlebar.list[c] then titlebar.list[c] = {} end
style = redutil.table.merge(default_style, style or {})
-- Make sure that there is never more than one titlebar for any given client
local ret
if not titlebar.list[c][style.position] then
local tfunction = get_titlebar_function(c, style.position)
local d = tfunction(c, style.size)
local context = { client = c, position = style.position }
local base = wibox.container.margin(nil, unpack(style.border_margin))
ret = drawable(d, context, "redbar")
ret:_inform_visible(true)
ret:set_bg(style.color.wibox)
-- add info to model
local model = {
layouts = {},
current = nil,
style = style,
size = style.size,
drawable = ret,
hidden = false,
cutted = false,
tfunction = tfunction,
base = base,
}
-- set titlebar base layout
ret:set_widget(base)
-- save titlebar info
titlebar.list[c][style.position] = model
c:connect_signal("unmanage", function() ret:_inform_visible(false) end)
else
ret = titlebar.list[c][style.position].drawable
end
return ret
end
-- Titlebar functions
-----------------------------------------------------------------------------------------------------------------------
-- Show client titlebar
------------------------------------------------------------
function titlebar.show(c, position)
local model = titlebar.get_model(c, position)
if model and model.hidden then
model.hidden = false
model.tfunction(c, not model.cutted and model.size or 0)
end
end
-- Hide client titlebar
------------------------------------------------------------
function titlebar.hide(c, position)
local model = titlebar.get_model(c, position)
if model and not model.hidden then
model.tfunction(c, 0)
model.hidden = true
end
end
-- Toggle client titlebar
------------------------------------------------------------
function titlebar.toggle(c, position)
local all_positions = position and { position } or positions
for _, pos in ipairs(all_positions) do
local model = titlebar.get_model(c, pos)
if model then
model.tfunction(c, model.hidden and not model.cutted and model.size or 0)
model.hidden = not model.hidden
end
end
end
-- Add titlebar view model
------------------------------------------------------------
function titlebar.add_layout(c, position, layout, size)
local model = titlebar.get_model(c, position)
if not model then return end
size = size or model.style.size
local l = { layout = layout, size = size }
table.insert(model.layouts, l)
if #model.layouts > titlebar._num then titlebar._num = #model.layouts end
if not model.current then
model.base:set_widget(layout)
model.current = 1
if model.size ~= size then
model.tfunction(c, size)
model.size = size
end
end
end
-- Switch titlebar view model
------------------------------------------------------------
function titlebar.switch(c, position, index)
local model = titlebar.get_model(c, position)
if not model or #model.layouts == 1 then return end
if index then
if not model.layouts[index] then return end
model.current = index
else
model.current = (model.current < #model.layouts) and (model.current + 1) or 1
end
local layout = model.layouts[model.current]
model.base:set_widget(layout.layout)
if not model.cutted and not model.hidden and model.size ~= layout.size then
model.tfunction(c, layout.size)
end
model.size = layout.size
end
-- Titlebar mass actions
-----------------------------------------------------------------------------------------------------------------------
-- Temporary hide client titlebar
------------------------------------------------------------
function titlebar.cut_all(cl, position)
cl = cl or titlebar.get_clients()
--local cutted = {}
local all_positions = position and { position } or positions
for _, pos in ipairs(all_positions) do
for _, c in ipairs(cl) do
local model = titlebar.get_model(c, pos)
if model and not model.cutted then
model.cutted = true
--table.insert(cutted, c)
if not model.hidden then model.tfunction(c, 0) end
end
end
end
--return cutted
end
-- Restore client titlebar if it was cutted
------------------------------------------------------------
function titlebar.restore_all(cl, position)
cl = cl or titlebar.get_clients()
local all_positions = position and { position } or positions
for _, pos in ipairs(all_positions) do
for _, c in ipairs(cl) do
local model = titlebar.get_model(c, pos)
if model and model.cutted then
model.cutted = false
if not model.hidden then model.tfunction(c, model.size) end
end
end
end
end
-- Mass actions
------------------------------------------------------------
function titlebar.toggle_all(cl, position)
cl = cl or titlebar.get_clients()
for _, c in pairs(cl) do titlebar.toggle(c, position) end
end
--function titlebar.switch_all(cl, position)
-- cl = cl or titlebar.get_clients()
-- for _, c in pairs(cl) do titlebar.switch(c, position) end
--end
function titlebar.show_all(cl, position)
cl = cl or titlebar.get_clients()
for _, c in pairs(cl) do titlebar.show(c, position) end
end
function titlebar.hide_all(cl, position)
cl = cl or titlebar.get_clients()
for _, c in pairs(cl) do titlebar.hide(c, position) end
end
-- Global layout switch
------------------------------------------------------------
function titlebar.global_switch(index)
titlebar._index = index or titlebar._index + 1
if titlebar._index > titlebar._num then titlebar._index = 1 end
for _, c in pairs(titlebar.get_clients()) do
for _, position in ipairs(positions) do
titlebar.switch(c, position, titlebar._index)
end
end
end
-- Titlebar indicators
-----------------------------------------------------------------------------------------------------------------------
titlebar.mark = {}
titlebar.button = {}
-- Client mark blank
------------------------------------------------------------
function titlebar.mark.base(_, style)
-- build widget
local widg = wibox.widget.base.make_widget()
widg._data = { color = style.color.gray }
widg._style = redutil.table.merge(default_mark_style, style or {})
-- widget setup
function widg:fit(_, _, width, height)
return width, height
end
function widg:draw(_, cr, width, height)
local d = math.tan(self._style.angle) * height
cr:set_source(color(self._data.color))
cr:move_to(0, height)
cr:rel_line_to(d, - height)
cr:rel_line_to(width - d, 0)
cr:rel_line_to(-d, height)
cr:close_path()
cr:fill()
end
-- user function
function widg:set_active(active)
self._data.color = active and style.color.main or style.color.gray
self:emit_signal("widget::redraw_needed")
end
-- widget width setup
widg:set_forced_width(style.size)
return widg
end
-- Client property indicator
------------------------------------------------------------
function titlebar.mark.property(c, prop, style)
local w = titlebar.mark.base(c, style)
w:set_active(c[prop])
c:connect_signal("property::" .. prop, function() w:set_active(c[prop]) end)
return w
end
-- Client focus indicator
------------------------------------------------------------
function titlebar.mark.focus(c, style)
local w = titlebar.mark.base(c, style)
c:connect_signal("focus", function() w:set_active(true) end)
c:connect_signal("unfocus", function() w:set_active(false) end)
return w
end
-- Client button blank
------------------------------------------------------------
function titlebar.button.base(icon, style, is_inactive)
style = redutil.table.merge(default_button_style, style or {})
-- widget
local widg = svgbox(style.list[icon] or style.list.unknown)
widg._current_color = style.color.icon
widg.is_under_mouse = false
-- state
function widg:set_active(active)
widg._current_color = active and style.color.main or style.color.icon
widg:set_color(widg.is_under_mouse and style.color.urgent or widg._current_color)
--self:emit_signal("widget::redraw_needed")
end
local function update(is_under_mouse)
widg.is_under_mouse = is_under_mouse
widg:set_color(widg.is_under_mouse and style.color.urgent or widg._current_color)
end
if not is_inactive then
widg:connect_signal("mouse::enter", function() update(true) end)
widg:connect_signal("mouse::leave", function() update(false) end)
end
widg:set_active(false)
return widg
end
-- Client focus button
------------------------------------------------------------
function titlebar.button.focus(c, style)
local w = titlebar.button.base("focus", style, true)
c:connect_signal("focus", function() w:set_active(true) end)
c:connect_signal("unfocus", function() w:set_active(false) end)
return w
end
-- Client property button
------------------------------------------------------------
function titlebar.button.property(c, prop, style)
local w = titlebar.button.base(prop, style)
w:set_active(c[prop])
w:buttons(awful.util.table.join(awful.button({ }, 1, function() c[prop] = not c[prop] end)))
c:connect_signal("property::" .. prop, function() w:set_active(c[prop]) end)
return w
end
-- Client close button
------------------------------------------------------------
function titlebar.button.close(c, style)
local w = titlebar.button.base("close", style)
w:buttons(awful.util.table.join(awful.button({ }, 1, function() c:kill() end)))
return w
end
-- Client name indicator
------------------------------------------------------------
function titlebar.label(c, style, is_highlighted)
style = redutil.table.merge(default_style, style or {})
local w = wibox.widget.textbox()
w:set_font(style.font)
w:set_align("center")
w._current_color = style.color.text
local function update()
local txt = awful.util.escape(c.name or "Unknown")
w:set_markup(string.format('<span color="%s">%s</span>', w._current_color, txt))
end
c:connect_signal("property::name", update)
if is_highlighted then
c:connect_signal("focus", function() w._current_color = style.color.main; update() end)
c:connect_signal("unfocus", function() w._current_color = style.color.text; update()end)
end
update()
return w
end
-- Remove from list on close
-----------------------------------------------------------------------------------------------------------------------
client.connect_signal("unmanage", function(c) titlebar.list[c] = nil end)
-- Config metatable to call titlebar module as function
-----------------------------------------------------------------------------------------------------------------------
function titlebar.mt:__call(...)
return titlebar.new(...)
end
return setmetatable(titlebar, titlebar.mt)

View File

@ -0,0 +1,66 @@
-- RedFlat util submodule
local cairo = require("lgi").cairo
local gears = require("gears")
local wibox = require("wibox")
local surface = require("gears.surface")
local base = {}
-- Functions
-----------------------------------------------------------------------------------------------------------------------
-- Advanced buttons setup
-- Copypasted from awful.widget.common
-- (c) 2008-2009 Julien Danjou
--------------------------------------------------------------------------------
function base.buttons(buttons, object)
if buttons then
local btns = {}
for _, b in ipairs(buttons) do
-- Create a proxy button object: it will receive the real
-- press and release events, and will propagate them the the
-- button object the user provided, but with the object as
-- argument.
local btn = button { modifiers = b.modifiers, button = b.button }
btn:connect_signal("press", function () b:emit_signal("press", object) end)
btn:connect_signal("release", function () b:emit_signal("release", object) end)
btns[#btns + 1] = btn
end
return btns
end
end
-- Create cairo surface from text (useful for themed icons replacement)
--------------------------------------------------------------------------------
function base.placeholder(args)
args = args or {}
local tb = wibox.widget({
markup = args.txt or "?",
align = "center",
valign = "center",
widget = wibox.widget.textbox
})
return surface.widget_to_surface(tb, args.width or 24, args.height or 24)
end
-- Create rectangle cairo surface image
--------------------------------------------------------------------------------
function base.image(width, height, geometry, color)
local image = cairo.ImageSurface.create(cairo.Format.ARGB32, width, height)
local cr = cairo.Context(image)
cr:set_source(gears.color(color or "#000000"))
cr:rectangle(geometry.x, geometry.y, geometry.width, geometry.height)
cr:fill()
return image
end
-- End
-----------------------------------------------------------------------------------------------------------------------
return base

View File

@ -0,0 +1,35 @@
-- RedFlat util submodule
local cairo = { textcentre = {} }
-- Functions
-----------------------------------------------------------------------------------------------------------------------
-- Draw text aligned by center
------------------------------------------------------------
function cairo.textcentre.full(cr, coord, text)
local ext = cr:text_extents(text)
cr:move_to(coord[1] - (ext.width/2 + ext.x_bearing), coord[2] - (ext.height/2 + ext.y_bearing))
cr:show_text(text)
end
-- Draw text aligned by center horizontal only
------------------------------------------------------------
function cairo.textcentre.horizontal(cr, coord, text)
local ext = cr:text_extents(text)
cr:move_to(coord[1] - (ext.width/2 + ext.x_bearing), coord[2])
cr:show_text(text)
end
-- Set font
------------------------------------------------------------
function cairo.set_font(cr, font)
cr:set_font_size(font.size)
cr:select_font_face(font.font, font.slant, font.face)
end
-- End
-----------------------------------------------------------------------------------------------------------------------
return cairo

View File

@ -0,0 +1,54 @@
-- RedFlat util submodule
local awful = require("awful")
local client = { floatset = {} }
-- Functions
-----------------------------------------------------------------------------------------------------------------------
local function size_correction(c, geometry, is_restore)
local sign = is_restore and - 1 or 1
local bg = sign * 2 * c.border_width
if geometry.width then geometry.width = geometry.width - bg end
if geometry.height then geometry.height = geometry.height - bg end
end
-- Client geometry correction by border width
--------------------------------------------------------------------------------
function client.fullgeometry(c, g)
local ng
if g then
if g.width and g.width <= 1 then return end
if g.height and g.height <= 1 then return end
size_correction(c, g, false)
ng = c:geometry(g)
else
ng = c:geometry()
end
size_correction(c, ng, true)
return ng
end
-- Smart swap include floating layout
--------------------------------------------------------------------------------
function client.swap(c1, c2)
local lay = awful.layout.get(c1.screen)
if awful.util.table.hasitem(client.floatset, lay) then
local g1, g2 = client.fullgeometry(c1), client.fullgeometry(c2)
client.fullgeometry(c1, g2)
client.fullgeometry(c2, g1)
end
c1:swap(c2)
end
-- End
-----------------------------------------------------------------------------------------------------------------------
return client

View File

@ -0,0 +1,126 @@
-- RedFlat util submodule
local wibox = require("wibox")
local awful = require("awful")
local desktop = { build = {} }
-- Functions
-----------------------------------------------------------------------------------------------------------------------
local function sum(t, n)
n = n or #t
local s = 0
for i = 1, n do s = s + t[i] end
return s
end
local function wposition(grid, n, workarea, dir)
local total = sum(grid[dir])
local full_gap = sum(grid.edge[dir])
local gap = #grid[dir] > 1 and (workarea[dir] - total - full_gap) / (#grid[dir] - 1) or 0
local current = sum(grid[dir], n - 1)
local pos = grid.edge[dir][1] + (n - 1) * gap + current
return pos
end
-- Calculate size and position for desktop widget
------------------------------------------------------------
function desktop.wgeometry(grid, place, workarea)
return {
x = wposition(grid, place[1], workarea, "width"),
y = wposition(grid, place[2], workarea, "height"),
width = grid.width[place[1]],
height = grid.height[place[2]]
}
end
-- Edge constructor
------------------------------------------------------------
function desktop.edge(direction, zone)
local edge = { area = {} }
edge.wibox = wibox({
bg = "#00000000", -- transparent without compositing manager
opacity = 0, -- transparent with compositing manager
ontop = true,
visible = true
})
edge.layout = wibox.layout.fixed[direction]()
edge.wibox:set_widget(edge.layout)
if zone then
for i, z in ipairs(zone) do
edge.area[i] = wibox.container.margin(nil, 0, 0, z)
edge.layout:add(edge.area[i])
end
end
return edge
end
-- Desktop widgets pack constructor
------------------------------------------------------------
function desktop.build.static(objects, buttons)
for _, object in ipairs(objects) do
object.wibox = wibox({ type = "desktop", visible = true, bg = object.body.style.color.wibox })
object.wibox:geometry(object.geometry)
object.wibox:set_widget(object.body.area)
if buttons then object.body.area:buttons(buttons) end
end
end
function desktop.build.dynamic(objects, s, bgimage, buttons)
s = s or mouse.screen
local bg = awful.util.file_readable(bgimage or "") and bgimage or nil
local last = { visible = true }
-- desktop bg wibox
local dwibox = wibox({ type = "desktop", visible = true, bg = "#00000000", bgimage = bg })
dwibox:geometry(s.workarea)
dwibox:setup({
buttons = buttons,
layout = wibox.layout.align.horizontal
})
-- individual wiboxes (perfomance wisely)
for _, object in ipairs(objects) do
local clr = object.body.style and object.body.style.color and object.body.style.color.wibox or nil
object.wibox = wibox({ type = "desktop", visible = true, bg = clr })
object.wibox:geometry(object.geometry)
object.wibox:set_widget(object.body.area)
if buttons then object.body.area:buttons(buttons) end
end
-- show widgets only for empty desktop
local function update_desktop()
local clients = s:get_clients()
local visible = #clients == 0
if visible ~= last.visible then
last.visible = visible
dwibox.visible = visible
for _, object in ipairs(objects) do object.wibox.visible = visible end
end
end
-- better way to check visible clients?
local client_signals = {
"property::sticky", "property::minimized", "property::screen", "property::hidden",
"tagged", "untagged", "list"
}
local tag_signals = { "property::selected", "property::activated" }
for _, sg in ipairs(client_signals) do client.connect_signal(sg, update_desktop) end
for _, sg in ipairs(tag_signals) do tag.connect_signal(sg, update_desktop) end
end
-- End
-----------------------------------------------------------------------------------------------------------------------
return desktop

View File

@ -0,0 +1,14 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat library --
-----------------------------------------------------------------------------------------------------------------------
local wrequire = function(table_, key)
local module = rawget(table_, key)
return module or require(table_._NAME .. '.' .. key)
end
local setmetatable = setmetatable
local lib = { _NAME = "redflat.util", wrequire = wrequire }
return setmetatable(lib, { __index = wrequire })

View File

@ -0,0 +1,67 @@
-- RedFlat util submodule
local unpack = unpack or table.unpack
local table = table
local awful = require("awful")
local key = {}
key.ignored_mod = { "Unknown", "Mod2" }
-- Functions
-----------------------------------------------------------------------------------------------------------------------
-- Build awful keys from reflat raw keys table
------------------------------------------------------------
function key.build(t)
local temp = {}
for _, v in ipairs(t) do
table.insert(temp, awful.key(unpack(v)))
end
return awful.util.table.join(unpack(temp))
end
-- Check if redflat raw key matched with awful prompt key
------------------------------------------------------------
function key.match_prompt(rawkey, mod, _key)
for m, _ in pairs(mod) do
if awful.util.table.hasitem(key.ignored_mod, m) then
mod[m] = nil
break
end
end
local modcheck = true
local count = 0
for k, _ in pairs(mod) do
modcheck = modcheck and awful.util.table.hasitem(rawkey[1], k)
count = count + 1
end
return #rawkey[1] == count and modcheck and _key:lower() == rawkey[2]:lower()
end
-- Check if redflat raw key matched with awful prompt key
------------------------------------------------------------
function key.match_grabber(rawkey, mod, _key)
for i, m in ipairs(mod) do
if awful.util.table.hasitem(key.ignored_mod, m) then
table.remove(mod, i)
break
end
end
local modcheck = #mod == #rawkey[1]
for _, v in ipairs(mod) do modcheck = modcheck and awful.util.table.hasitem(rawkey[1], v) end
return modcheck and _key:lower() == rawkey[2]:lower()
end
-- End
-----------------------------------------------------------------------------------------------------------------------
return key

View File

@ -0,0 +1,64 @@
-- RedFlat util submodule
local awful = require("awful")
local placement = {}
local direction = { x = "width", y = "height" }
-- Functions
-----------------------------------------------------------------------------------------------------------------------
function placement.add_gap(geometry, gap)
return {
x = geometry.x + gap,
y = geometry.y + gap,
width = geometry.width - 2 * gap,
height = geometry.height - 2 * gap
}
end
function placement.no_offscreen(object, gap, area)
local geometry = object:geometry()
local border = object.border_width
local screen_idx = object.screen or awful.screen.getbycoord(geometry.x, geometry.y)
area = area or screen[screen_idx].workarea
if gap then area = placement.add_gap(area, gap) end
for coord, dim in pairs(direction) do
if geometry[coord] + geometry[dim] + 2 * border > area[coord] + area[dim] then
geometry[coord] = area[coord] + area[dim] - geometry[dim] - 2*border
elseif geometry[coord] < area[coord] then
geometry[coord] = area[coord]
end
end
object:geometry(geometry)
end
local function centered_base(is_h, is_v)
return function(object, gap, area)
local geometry = object:geometry()
local new_geometry = {}
local screen_idx = object.screen or awful.screen.getbycoord(geometry.x, geometry.y)
area = area or screen[screen_idx].geometry
if gap then area = placement.add_gap(area, gap) end
if is_h then new_geometry.x = area.x + (area.width - geometry.width) / 2 - object.border_width end
if is_v then new_geometry.y = area.y + (area.height - geometry.height) / 2 - object.border_width end
return object:geometry(new_geometry)
end
end
placement.centered = setmetatable({}, {
__call = function(_, ...) return centered_base(true, true)(...) end
})
placement.centered.horizontal = centered_base(true, false)
placement.centered.vertical = centered_base(false, true)
-- End
-----------------------------------------------------------------------------------------------------------------------
return placement

View File

@ -0,0 +1,32 @@
-- RedFlat util submodule
local io = io
local assert = assert
local read = {}
-- Functions
-----------------------------------------------------------------------------------------------------------------------
function read.file(path)
local file = io.open(path)
if file then
local output = file:read("*a")
file:close()
return output
else
return nil
end
end
function read.output(cmd)
local file = assert(io.popen(cmd, 'r'))
local output = file:read('*all')
file:close()
return output
end
-- End
-----------------------------------------------------------------------------------------------------------------------
return read

View File

@ -0,0 +1,45 @@
-- RedFlat util submodule
local awful = require("awful")
local table_ = {}
-- Functions
-----------------------------------------------------------------------------------------------------------------------
-- Merge two table to new one
------------------------------------------------------------
function table_.merge(t1, t2)
local ret = awful.util.table.clone(t1)
for k, v in pairs(t2) do
if type(v) == "table" and ret[k] and type(ret[k]) == "table" then
ret[k] = table_.merge(ret[k], v)
else
ret[k] = v
end
end
return ret
end
-- Check if deep key exists
------------------------------------------------------------
function table_.check(t, s)
local v = t
for key in string.gmatch(s, "([^%.]+)(%.?)") do
if v[key] then
v = v[key]
else
return nil
end
end
return v
end
-- End
-----------------------------------------------------------------------------------------------------------------------
return table_

View File

@ -0,0 +1,41 @@
-- RedFlat util submodule
local math = math
local string = string
local text = {}
-- Functions
-----------------------------------------------------------------------------------------------------------------------
-- Format string to number with minimum length
------------------------------------------------------------
function text.oformat(v, w)
local p = math.ceil(math.log10(v))
local prec = v <= 10 and w - 1 or p > w and 0 or w - p
return string.format('%.' .. prec .. 'f', v)
end
-- Format output for destop widgets
------------------------------------------------------------
function text.dformat(value, unit, w, spacer)
w = w or 3
spacer = spacer or " "
local res = value
local add = ""
for _, v in pairs(unit) do
if value > v[2] then
res = math.abs(value/v[2])
add = v[1]
end
end
return text.oformat(res, w) .. spacer .. add
end
-- End
-----------------------------------------------------------------------------------------------------------------------
return text

View File

@ -0,0 +1,99 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat battery widget --
-----------------------------------------------------------------------------------------------------------------------
-- Battery charge monitoring widget
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local beautiful = require("beautiful")
local timer = require("gears.timer")
local rednotify = require("redflat.float.notify")
local tooltip = require("redflat.float.tooltip")
local monitor = require("redflat.gauge.monitor.plain")
local redutil = require("redflat.util")
-- Initialize tables and vars for module
-----------------------------------------------------------------------------------------------------------------------
local battery = { mt = {} }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
timeout = 60,
width = nil,
widget = monitor.new,
notify = {},
levels = { 0.05, 0.1, 0.15 }
}
return redutil.table.merge(style, redutil.table.check(beautiful, "widget.battery") or {})
end
-- Support fucntions
-----------------------------------------------------------------------------------------------------------------------
local get_level = function(value, line)
for _, v in ipairs(line) do
if value < v then return v end
end
end
-- Create a system monitoring widget with additional notification
-----------------------------------------------------------------------------------------------------------------------
function battery.new(args, style)
-- Initialize vars
--------------------------------------------------------------------------------
args = args or {}
style = redutil.table.merge(default_style(), style or {})
-- Create monitor widget
--------------------------------------------------------------------------------
local widg = style.widget(style.monitor)
widg._last = { value = 1, level = 1 }
-- Set tooltip
--------------------------------------------------------------------------------
widg._tp = tooltip({ objects = { widg } }, style.tooltip)
-- Set update timer
--------------------------------------------------------------------------------
widg._update = function()
local state = args.func(args.arg)
widg:set_value(state.value)
widg:set_alert(state.alert)
widg._tp:set_text(state.text)
if state.value <= widg._last.value then
local level = get_level(state.value, style.levels)
if level and level ~= widg._last.level then
widg._last.level = level
local warning = string.format("Battery charge < %.0f%%", level * 100)
rednotify:show(redutil.table.merge({ text = warning }, style.notify))
end
else
widg._last.level = nil
end
widg._last.value = state.value
end
widg._timer = timer({ timeout = style.timeout })
widg._timer:connect_signal("timeout", function() widg._update() end)
widg._timer:start()
widg._timer:emit_signal("timeout")
--------------------------------------------------------------------------------
return widg
end
-- Config metatable to call module as function
-----------------------------------------------------------------------------------------------------------------------
function battery.mt:__call(...)
return battery.new(...)
end
return setmetatable(battery, battery.mt)

View File

@ -0,0 +1,125 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat binary clock widget --
-----------------------------------------------------------------------------------------------------------------------
-- Why not?
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local os = os
local wibox = require("wibox")
local beautiful = require("beautiful")
local gears = require("gears")
local color = require("gears.color")
local tooltip = require("redflat.float.tooltip")
local redutil = require("redflat.util")
-- Initialize tables and vars for module
-----------------------------------------------------------------------------------------------------------------------
local binclock = { mt = {} }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
width = 60,
tooltip = {},
dot = { size = 5 },
color = { main = "#b1222b", gray = "#575757" }
}
return redutil.table.merge(style, redutil.table.check(beautiful, "widget.binclock") or {})
end
-- Support functions
-----------------------------------------------------------------------------------------------------------------------
local function binary_time(num)
local binary = {}
for i = 6, 1, -1 do
local rest = num % 2
binary[i] = rest
num = math.floor((num - rest) / 2)
end
return binary
end
-- Create widget.
-----------------------------------------------------------------------------------------------------------------------
function binclock.new(args, style)
-- Initialize vars
--------------------------------------------------------------------------------
args = args or {}
local timeout = args.timeout or 60
style = redutil.table.merge(default_style(), style or {})
-- Create widget
--------------------------------------------------------------------------------
local widg = wibox.widget.base.make_widget()
widg._data = {
time = { {}, {}, {} },
width = style.width or nil
}
-- User functions
------------------------------------------------------------
function widg:update()
local date = os.date('*t')
self._data.time = { binary_time(date.hour), binary_time(date.min), binary_time(date.sec) }
self:emit_signal("widget::redraw_needed")
end
-- Fit
------------------------------------------------------------
function widg:fit(_, width, height)
if self._data.width then
return math.min(width, self._data.width), height
else
return width, height
end
end
function widg:draw(_, cr, width, height)
local dx = (width - style.dot.size) / 5
local dy = (height - style.dot.size) / 2
for i = 1, 3 do
for j = 1, 6 do
cr:set_source(color(self._data.time[i][j] == 1 and style.color.main or style.color.gray ))
cr:rectangle((j -1) * dx, (i -1) * dy, style.dot.size, style.dot.size)
cr:fill()
end
end
end
-- Set tooltip if need
--------------------------------------------------------------------------------
local tp
if args.dateformat then tp = tooltip({ objects = { widg } }, style.tooltip) end
-- Set update timer
--------------------------------------------------------------------------------
local timer = gears.timer({ timeout = timeout })
timer:connect_signal("timeout",
function()
widg:update()
if args.dateformat then tp:set_text(os.date(args.dateformat)) end
end)
timer:start()
timer:emit_signal("timeout")
--------------------------------------------------------------------------------
return widg
end
-- Config metatable to call textclock module as function
-----------------------------------------------------------------------------------------------------------------------
function binclock.mt:__call(...)
return binclock.new(...)
end
return setmetatable(binclock, binclock.mt)

View File

@ -0,0 +1,10 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat library --
-----------------------------------------------------------------------------------------------------------------------
local wrequire = require("redflat.util").wrequire
local setmetatable = setmetatable
local lib = { _NAME = "redflat.widget" }
return setmetatable(lib, { __index = wrequire })

View File

@ -0,0 +1,135 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat keyboard layout indicator widget --
-----------------------------------------------------------------------------------------------------------------------
-- Indicate and switch keybord layout
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local table = table
local awful = require("awful")
local beautiful = require("beautiful")
local tooltip = require("redflat.float.tooltip")
local redmenu = require("redflat.menu")
local redutil = require("redflat.util")
local svgbox = require("redflat.gauge.svgbox")
-- Initialize tables and vars for module
-----------------------------------------------------------------------------------------------------------------------
local keybd = { mt = {} }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
icon = redutil.base.placeholder(),
micon = { blank = redutil.base.placeholder({ txt = " " }),
check = redutil.base.placeholder({ txt = "+" }) },
menu = { color = { right_icon = "#a0a0a0" } },
layout_color = { "#a0a0a0", "#b1222b" },
fallback_color = "#32882d",
}
return redutil.table.merge(style, redutil.table.check(beautiful, "widget.keyboard") or {})
end
-- Initialize layout menu
-----------------------------------------------------------------------------------------------------------------------
function keybd:init(layouts, style)
-- initialize vars
style = redutil.table.merge(default_style(), style or {})
self.layouts = layouts or {}
self.style = style
self.objects = {}
-- tooltip
self.tp = tooltip({ objects = {} }, style.tooltip)
-- construct list of layouts
local menu_items = {}
for i = 1, #layouts do
local command = function() awesome.xkb_set_layout_group(i - 1) end
table.insert(menu_items, { layouts[i], command, nil, style.micon.blank })
end
-- initialize menu
self.menu = redmenu({ hide_timeout = 1, theme = style.menu, items = menu_items })
if self.menu.items[1].right_icon then
self.menu.items[1].right_icon:set_image(style.micon.check)
end
-- update layout data
self.update = function()
local layout = awesome.xkb_get_layout_group() + 1
for _, w in ipairs(self.objects) do
w:set_color(style.layout_color[layout] or style.fallback_color)
end
-- update tooltip
keybd.tp:set_text(self.layouts[layout] or "Unknown")
-- update menu
for i = 1, #self.layouts do
local mark = layout == i and style.micon.check or style.micon.blank
keybd.menu.items[i].right_icon:set_image(mark)
end
end
awesome.connect_signal("xkb::group_changed", self.update)
end
-- Show layout menu
-----------------------------------------------------------------------------------------------------------------------
function keybd:toggle_menu()
if not self.menu then return end
if self.menu.wibox.visible then
self.menu:hide()
else
awful.placement.under_mouse(self.menu.wibox)
awful.placement.no_offscreen(self.menu.wibox)
self.menu:show({ coords = { x = self.menu.wibox.x, y = self.menu.wibox.y } })
end
end
-- Toggle layout
-----------------------------------------------------------------------------------------------------------------------
function keybd:toggle(reverse)
if not self.layouts then return end
local layout = awesome.xkb_get_layout_group()
if reverse then
layout = layout > 0 and (layout - 1) or (#self.layouts - 1)
else
layout = layout < (#self.layouts - 1) and (layout + 1) or 0
end
awesome.xkb_set_layout_group(layout)
end
-- Create a new keyboard indicator widget
-----------------------------------------------------------------------------------------------------------------------
function keybd.new(style)
style = style or {}
if not keybd.menu then keybd:init({}) end
local widg = svgbox(style.icon or keybd.style.icon)
widg:set_color(keybd.style.layout_color[1])
table.insert(keybd.objects, widg)
keybd.tp:add_to_object(widg)
keybd.update()
return widg
end
-- Config metatable to call keybd module as function
-----------------------------------------------------------------------------------------------------------------------
function keybd.mt:__call(...)
return keybd.new(...)
end
return setmetatable(keybd, keybd.mt)

View File

@ -0,0 +1,168 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat layoutbox widget --
-----------------------------------------------------------------------------------------------------------------------
-- Paintbox widget used to display layout
-- Layouts menu added
-----------------------------------------------------------------------------------------------------------------------
-- Some code was taken from
------ awful.widget.layoutbox v3.5.2
------ (c) 2009 Julien Danjou
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local ipairs = ipairs
local table = table
local awful = require("awful")
local layout = require("awful.layout")
local beautiful = require("beautiful")
local redmenu = require("redflat.menu")
local tooltip = require("redflat.float.tooltip")
local redutil = require("redflat.util")
local svgbox = require("redflat.gauge.svgbox")
-- Initialize tables and vars for module
-----------------------------------------------------------------------------------------------------------------------
local layoutbox = { mt = {} }
local last_tag
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
icon = { unknown = redutil.base.placeholder() },
micon = { blank = redutil.base.placeholder({ txt = " " }),
check = redutil.base.placeholder({ txt = "+" }) },
name_alias = {},
menu = { color = { right_icon = "#a0a0a0", left_icon = "#a0a0a0" } },
color = { icon = "#a0a0a0" }
}
return redutil.table.merge(style, redutil.table.check(beautiful, "widget.layoutbox") or {})
end
-- Initialize layoutbox
-----------------------------------------------------------------------------------------------------------------------
function layoutbox:init(layouts, style)
style = style or default_style()
-- Set tooltip
------------------------------------------------------------
layoutbox.tp = tooltip({})
-- Construct layout list
------------------------------------------------------------
local items = {}
for _, l in ipairs(layouts) do
local layout_name = layout.getname(l)
local icon = style.icon[layout_name] or style.icon.unknown
local text = style.name_alias[layout_name] or layout_name
table.insert(items, { text, function() layout.set (l, last_tag) end, icon, style.micon.blank })
end
-- Update tooltip function
------------------------------------------------------------
function self:update_tooltip(layout_name)
self.tp:set_text(style.name_alias[layout_name] or layout_name)
end
-- Update menu function
------------------------------------------------------------
function self:update_menu(t)
local cl = awful.tag.getproperty(t, "layout")
for i, l in ipairs(layouts) do
local mark = cl == l and style.micon.check or style.micon.blank
if self.menu.items[i].right_icon then
self.menu.items[i].right_icon:set_image(mark)
end
end
end
-- Initialize menu
------------------------------------------------------------
self.menu = redmenu({ theme = style.menu, items = items })
end
-- Show layout menu
-----------------------------------------------------------------------------------------------------------------------
function layoutbox:toggle_menu(t)
if self.menu.wibox.visible and t == last_tag then
self.menu:hide()
else
if self.menu.wibox.visible then self.menu.wibox.visible = false end
awful.placement.under_mouse(self.menu.wibox)
awful.placement.no_offscreen(self.menu.wibox)
last_tag = t
self.menu:show({coords = {x = self.menu.wibox.x, y = self.menu.wibox.y}})
self:update_menu(last_tag)
end
end
-- Create a layoutbox widge
-- @param screen The screen number that the layout will be represented for
-- @param layouts List of layouts
-----------------------------------------------------------------------------------------------------------------------
function layoutbox.new(args, style)
-- Initialize vars
--------------------------------------------------------------------------------
args = args or {}
local layouts = args.layouts or awful.layout.layouts
local s = args.screen or 1
style = redutil.table.merge(default_style(), style or {})
local w = svgbox()
w:set_color(style.color.icon)
if not layoutbox.menu then layoutbox:init(layouts, style) end
-- Set tooltip
--------------------------------------------------------------------------------
layoutbox.tp:add_to_object(w)
-- Update function
--------------------------------------------------------------------------------
local function update()
local layout_name = layout.getname(layout.get(s))
w:set_image(style.icon[layout_name] or style.icon.unknown)
layoutbox:update_tooltip(layout_name)
if layoutbox.menu.wibox.visible then
layoutbox:update_menu(last_tag)
end
end
-- Set signals
--------------------------------------------------------------------------------
tag.connect_signal("property::selected", update)
tag.connect_signal("property::layout", update)
w:connect_signal("mouse::enter",
function()
local layout_name = layout.getname(layout.get(s))
layoutbox:update_tooltip(layout_name)
end
)
w:connect_signal("mouse::leave",
function()
if layoutbox.menu.hidetimer and layoutbox.menu.wibox.visible then
layoutbox.menu.hidetimer:start()
end
end
)
--------------------------------------------------------------------------------
update()
return w
end
-- Config metatable to call layoutbox module as function
-----------------------------------------------------------------------------------------------------------------------
function layoutbox.mt:__call(...)
return layoutbox.new(...)
end
return setmetatable(layoutbox, layoutbox.mt)

View File

@ -0,0 +1,196 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat mail widget --
-----------------------------------------------------------------------------------------------------------------------
-- Check if new mail available using python scripts or curl shell command
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local table = table
local tonumber = tonumber
local awful = require("awful")
local beautiful = require("beautiful")
local timer = require("gears.timer")
local naughty = require("naughty")
local rednotify = require("redflat.float.notify")
local tooltip = require("redflat.float.tooltip")
local redutil = require("redflat.util")
local svgbox = require("redflat.gauge.svgbox")
local startup = require("redflat.startup")
-- Initialize tables for module
-----------------------------------------------------------------------------------------------------------------------
local mail = { objects = {}, mt = {} }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
icon = redutil.base.placeholder(),
notify = {},
need_notify = true,
firstrun = false,
color = { main = "#b1222b", icon = "#a0a0a0" }
}
return redutil.table.merge(style, redutil.table.check(beautiful, "widget.mail") or {})
end
-- Mail check functions
-----------------------------------------------------------------------------------------------------------------------
mail.checkers = {}
mail.checkers["script"] = { type = "spawn" }
mail.checkers["script"].action = function(args)
return args.script
end
mail.checkers["curl_imap"] = { type = "spawn" }
mail.checkers["curl_imap"].action = function(args)
local port = args.port or 993
local request = "-X 'STATUS INBOX (UNSEEN)'"
local head_command = "curl --connect-timeout 5 -fsm 5"
local curl_req = string.format("%s --url imaps://%s:%s/INBOX -u %s:%s %s -k",
head_command, args.server, port, args.mail, args.password, request)
return curl_req
end
mail.checkers["imap"] = { type = "plain" }
mail.checkers["imap"].action = function(args)
-- require imap4 library
-- need to luarock install imap4 library
local imap4 = require "imap4"
-- grab args or set default values
-- gmail requires 'Less secure app access'
local port = args.port or 993
local url = args.server or "outlook.office365.com"
local mailbox = args.mailbox or "Inbox"
-- setup connection
local status, connection = pcall(imap4, url, port, {protocol = 'tlsv1_2'})
if status ~= false and connection:isCapable('IMAP4rev1') then
connection:login(args.username, args.password)
-- get information
local stat = connection:status(mailbox, {'UNSEEN'})
-- close connection
connection:logout()
-- return unseen email count
return stat.UNSEEN
else
return "0"
end
end
-- Initialize mails structure
-----------------------------------------------------------------------------------------------------------------------
function mail:init(args, style)
-- Initialize vars
--------------------------------------------------------------------------------
args = args or {}
local count = 0
local checks = 0
local force_notify = false
local update_timeout = args.update_timeout or 3600
local maillist = args.maillist or {}
style = redutil.table.merge(default_style(), style or {})
self.style = style
-- Set tooltip
--------------------------------------------------------------------------------
self.tp = tooltip(nil, style.tooltip)
self.tp:set_text("?")
-- Update info function
--------------------------------------------------------------------------------
local function mail_count(output)
local c = tonumber(string.match(output, "%d+"))
checks = checks + 1
if c then
count = count + c
if style.need_notify and (count > 0 or force_notify and checks == #maillist) then
rednotify:show(redutil.table.merge({ text = count .. " new messages" }, style.notify))
end
end
self.tp:set_text(count .. " new messages")
local color = count > 0 and style.color.main or style.color.icon
for _, widg in ipairs(mail.objects) do widg:set_color(color) end
end
self.check_updates = function(is_force)
count = 0
checks = 0
force_notify = is_force
for _, cmail in ipairs(maillist) do
if mail.checkers[cmail.checker].type == "plain" then
mail_count(mail.checkers[cmail.checker].action(cmail))
elseif mail.checkers[cmail.checker].type == "spawn" then
awful.spawn.easy_async(mail.checkers[cmail.checker].action(cmail), mail_count)
end
end
end
-- Set update timer
--------------------------------------------------------------------------------
self.timer = timer({ timeout = update_timeout })
self.timer:connect_signal("timeout", function() self.check_updates() end)
self.timer:start()
if style.firstrun and startup.is_startup then self.timer:emit_signal("timeout") end
end
-- Create a new mail widget
-----------------------------------------------------------------------------------------------------------------------
function mail.new(style)
if not mail.style then
naughty.notify({ title = "Warning!", text = "Mail widget doesn't configured" })
mail:init({})
end
-- Initialize vars
--------------------------------------------------------------------------------
style = redutil.table.merge(mail.style, style or {})
-- Create widget
--------------------------------------------------------------------------------
local widg = svgbox(style.icon)
widg:set_color(style.color.icon)
table.insert(mail.objects, widg)
-- Set tooltip
--------------------------------------------------------------------------------
mail.tp:add_to_object(widg)
--------------------------------------------------------------------------------
return widg
end
-- Update mail info for every widget
-----------------------------------------------------------------------------------------------------------------------
function mail:update(is_force)
self.check_updates(is_force)
end
-- Config metatable to call mail module as function
-----------------------------------------------------------------------------------------------------------------------
function mail.mt:__call(...)
return mail.new(...)
end
return setmetatable(mail, mail.mt)

View File

@ -0,0 +1,175 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat minitray --
-----------------------------------------------------------------------------------------------------------------------
-- Tray located on separate wibox
-- minitray:toggle() used to show/hide wibox
-- Widget with graphical counter to show how many apps placed in the system tray
-----------------------------------------------------------------------------------------------------------------------
-- Some code was taken from
------ minitray
------ http://awesome.naquadah.org/wiki/Minitray
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local table = table
local wibox = require("wibox")
local awful = require("awful")
local beautiful = require("beautiful")
local redutil = require("redflat.util")
local dotcount = require("redflat.gauge.graph.dots")
local tooltip = require("redflat.float.tooltip")
-- Initialize tables and wibox
-----------------------------------------------------------------------------------------------------------------------
local minitray = { widgets = {}, mt = {} }
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
dotcount = {},
geometry = { height = 40 },
set_position = nil,
screen_gap = 0,
border_width = 2,
color = { wibox = "#202020", border = "#575757" },
shape = nil
}
return redutil.table.merge(style, redutil.table.check(beautiful, "widget.minitray") or {})
end
-- Initialize minitray floating window
-----------------------------------------------------------------------------------------------------------------------
function minitray:init(style)
-- Create wibox for tray
--------------------------------------------------------------------------------
local wargs = {
ontop = true,
bg = style.color.wibox,
border_width = style.border_width,
border_color = style.color.border,
shape = style.shape
}
self.wibox = wibox(wargs)
self.wibox:geometry(style.geometry)
self.geometry = style.geometry
self.screen_gap = style.screen_gap
self.set_position = style.set_position
-- Create tooltip
--------------------------------------------------------------------------------
self.tp = tooltip({}, style.tooltip)
-- Set tray
--------------------------------------------------------------------------------
local l = wibox.layout.align.horizontal()
self.tray = wibox.widget.systray()
l:set_middle(self.tray)
self.wibox:set_widget(l)
-- update geometry if tray icons change
self.tray:connect_signal('widget::redraw_needed', function()
self:update_geometry()
end)
end
-- Show/hide functions for wibox
-----------------------------------------------------------------------------------------------------------------------
-- Update Geometry
--------------------------------------------------------------------------------
function minitray:update_geometry()
-- Set wibox size and position
------------------------------------------------------------
local items = awesome.systray()
if items == 0 then items = 1 end
self.wibox:geometry({ width = self.geometry.width or self.geometry.height * items })
if self.set_position then
self.set_position(self.wibox)
else
awful.placement.under_mouse(self.wibox)
end
redutil.placement.no_offscreen(self.wibox, self.screen_gap, mouse.screen.workarea)
end
-- Show
--------------------------------------------------------------------------------
function minitray:show()
self:update_geometry()
self.wibox.visible = true
end
-- Hide
--------------------------------------------------------------------------------
function minitray:hide()
self.wibox.visible = false
end
-- Toggle
--------------------------------------------------------------------------------
function minitray:toggle()
if self.wibox.visible then
self:hide()
else
self:show()
end
end
-- Create a new tray widget
-- @param args.timeout Update interval
-- @param style Settings for dotcount widget
-----------------------------------------------------------------------------------------------------------------------
function minitray.new(_, style)
-- Initialize vars
--------------------------------------------------------------------------------
-- args = args or {} -- usesless now, leave it be for backward compatibility and future cases
style = redutil.table.merge(default_style(), style or {})
-- Initialize minitray window
--------------------------------------------------------------------------------
if not minitray.wibox then
minitray:init(style)
end
-- Create tray widget
--------------------------------------------------------------------------------
local widg = dotcount(style.dotcount)
table.insert(minitray.widgets, widg)
-- Set tooltip
--------------------------------------------------------------------------------
minitray.tp:add_to_object(widg)
-- Set update timer
--------------------------------------------------------------------------------
function widg:update()
local appcount = awesome.systray()
self:set_num(appcount)
minitray.tp:set_text(appcount .. " apps")
end
minitray.tray:connect_signal('widget::redraw_needed', function() widg:update() end)
--------------------------------------------------------------------------------
return widg
end
-- Config metatable to call minitray module as function
-----------------------------------------------------------------------------------------------------------------------
function minitray.mt:__call(...)
return minitray.new(...)
end
return setmetatable(minitray, minitray.mt)

View File

@ -0,0 +1,101 @@
-----------------------------------------------------------------------------------------------------------------------
-- RedFlat network widget --
-----------------------------------------------------------------------------------------------------------------------
-- Network speed monitoring widget
-----------------------------------------------------------------------------------------------------------------------
-- Grab environment
-----------------------------------------------------------------------------------------------------------------------
local setmetatable = setmetatable
local beautiful = require("beautiful")
local timer = require("gears.timer")
local redutil = require("redflat.util")
local monitor = require("redflat.gauge.icon.double")
local tooltip = require("redflat.float.tooltip")
local system = require("redflat.system")
-- Initialize tables and vars for module
-----------------------------------------------------------------------------------------------------------------------
local net = { mt = {} }
local default_args = {
speed = { up = 10*1024, down = 10*1024 },
autoscale = true,
interface = "eth0"
}
-- Generate default theme vars
-----------------------------------------------------------------------------------------------------------------------
local function default_style()
local style = {
widget = monitor.new,
timeout = 5,
digits = 2
}
return redutil.table.merge(style, redutil.table.check(beautiful, "widget.net") or {})
end
-- Create a new network widget
-- @param args.timeout Update interval
-- @param args.interface Network interface
-- @param args.autoscale Scale bars, true by default
-- @param args.speed.up Max upload speed in bytes
-- @param args.speed.down Max download speed in bytes
-- @param style Settings for doublebar widget
-----------------------------------------------------------------------------------------------------------------------
function net.new(args, style)
-- Initialize vars
--------------------------------------------------------------------------------
local storage = {}
local unit = {{ "B", 1 }, { "KB", 1024 }, { "MB", 1024^2 }, { "GB", 1024^3 }}
args = redutil.table.merge(default_args, args or {})
style = redutil.table.merge(default_style(), style or {})
-- Create monitor widget
--------------------------------------------------------------------------------
local widg = style.widget(style.monitor)
-- Set tooltip
--------------------------------------------------------------------------------
local tp = tooltip({ objects = { widg } }, style.tooltip)
-- Set update timer
--------------------------------------------------------------------------------
local t = timer({ timeout = style.timeout })
t:connect_signal("timeout",
function()
local state = system.net_speed(args.interface, storage)
widg:set_value({1,1})
if args.autoscale then
if state[1] > args.speed.up then args.speed.up = state[1] end
if state[2] > args.speed.down then args.speed.down = state[2] end
end
if args.alert then
widg:set_alert(state[1] > args.alert.up or state[2] > args.alert.down)
end
widg:set_value({ state[2]/args.speed.down, state[1]/args.speed.up })
tp:set_text(
"" .. redutil.text.dformat(state[2], unit, style.digits, " ")
.. "" .. redutil.text.dformat(state[1], unit, style.digits, " ")
)
end
)
t:start()
t:emit_signal("timeout")
--------------------------------------------------------------------------------
return widg
end
-- Config metatable to call net module as function
-----------------------------------------------------------------------------------------------------------------------
function net.mt:__call(...)
return net.new(...)
end
return setmetatable(net, net.mt)

Some files were not shown because too many files have changed in this diff Show More