Update and add bling task_preview, tag_preview and window_switcher Add catppuccin-macchiato Use my weather key Update keybindings Update autorun Fix sliders
532 lines
14 KiB
Lua
532 lines
14 KiB
Lua
local cairo = require("lgi").cairo
|
|
local mouse = mouse
|
|
local screen = screen
|
|
local wibox = require('wibox')
|
|
local table = table
|
|
local keygrabber = keygrabber
|
|
local math = require('math')
|
|
local awful = require('awful')
|
|
local gears = require("gears")
|
|
local timer = gears.timer
|
|
local client = client
|
|
awful.client = require('awful.client')
|
|
|
|
local naughty = require("naughty")
|
|
local string = string
|
|
local tostring = tostring
|
|
local tonumber = tonumber
|
|
local debug = debug
|
|
local pairs = pairs
|
|
local unpack = unpack or table.unpack
|
|
|
|
local surface = cairo.ImageSurface(cairo.Format.RGB24,20,20)
|
|
local cr = cairo.Context(surface)
|
|
|
|
local _M = {}
|
|
|
|
-- settings
|
|
|
|
_M.settings = {
|
|
preview_box = true,
|
|
preview_box_bg = "#18192611",
|
|
preview_box_border = "#18192611",
|
|
preview_box_fps = 30,
|
|
preview_box_delay = 100,
|
|
preview_box_title_font = {"Ubuntu","italic","normal"},
|
|
preview_box_title_font_size_factor = 0.8,
|
|
preview_box_title_color = {202,211,245,1},
|
|
|
|
client_opacity = true,
|
|
client_opacity_value_selected = 1,
|
|
client_opacity_value_in_focus = 0.5,
|
|
client_opacity_value = 0.5,
|
|
|
|
cycle_raise_client = true,
|
|
}
|
|
|
|
-- Create a wibox to contain all the client-widgets
|
|
_M.preview_wbox = wibox({ width = screen[mouse.screen].geometry.width })
|
|
_M.preview_wbox.border_width = 3
|
|
_M.preview_wbox.ontop = true
|
|
_M.preview_wbox.visible = false
|
|
|
|
_M.preview_live_timer = timer({ timeout = 1/_M.settings.preview_box_fps })
|
|
_M.preview_widgets = {}
|
|
|
|
_M.altTabTable = {}
|
|
_M.altTabIndex = 1
|
|
|
|
_M.source = string.sub(debug.getinfo(1,'S').source, 2)
|
|
_M.path = string.sub(_M.source, 1, string.find(_M.source, "/[^/]*$"))
|
|
_M.noicon = _M.path .. "noicon.png"
|
|
|
|
-- simple function for counting the size of a table
|
|
function _M.tableLength(T)
|
|
local count = 0
|
|
for _ in pairs(T) do count = count + 1 end
|
|
return count
|
|
end
|
|
|
|
-- this function returns the list of clients to be shown.
|
|
function _M.getClients()
|
|
local clients = {}
|
|
|
|
-- Get focus history for current tag
|
|
local s = mouse.screen;
|
|
local idx = 0
|
|
local c = awful.client.focus.history.get(s, idx)
|
|
|
|
while c do
|
|
table.insert(clients, c)
|
|
|
|
idx = idx + 1
|
|
c = awful.client.focus.history.get(s, idx)
|
|
end
|
|
|
|
-- Minimized clients will not appear in the focus history
|
|
-- Find them by cycling through all clients, and adding them to the list
|
|
-- if not already there.
|
|
-- This will preserve the history AND enable you to focus on minimized clients
|
|
|
|
local t = s.selected_tag
|
|
local all = client.get(s)
|
|
|
|
for i = 1, #all do
|
|
local c = all[i]
|
|
local ctags = c:tags();
|
|
|
|
-- check if the client is on the current tag
|
|
local isCurrentTag = false
|
|
for j = 1, #ctags do
|
|
if t == ctags[j] then
|
|
isCurrentTag = true
|
|
break
|
|
end
|
|
end
|
|
|
|
if isCurrentTag then
|
|
-- check if client is already in the history
|
|
-- if not, add it
|
|
local addToTable = true
|
|
for k = 1, #clients do
|
|
if clients[k] == c then
|
|
addToTable = false
|
|
break
|
|
end
|
|
end
|
|
|
|
|
|
if addToTable then
|
|
table.insert(clients, c)
|
|
end
|
|
end
|
|
end
|
|
|
|
return clients
|
|
end
|
|
|
|
-- here we populate altTabTable using the list of clients taken from
|
|
-- _M.getClients(). In case we have altTabTable with some value, the list of the
|
|
-- old known clients is restored.
|
|
function _M.populateAltTabTable()
|
|
local clients = _M.getClients()
|
|
|
|
if _M.tableLength(_M.altTabTable) then
|
|
for ci = 1, #clients do
|
|
for ti = 1, #_M.altTabTable do
|
|
if _M.altTabTable[ti].client == clients[ci] then
|
|
_M.altTabTable[ti].client.opacity = _M.altTabTable[ti].opacity
|
|
_M.altTabTable[ti].client.minimized = _M.altTabTable[ti].minimized
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
_M.altTabTable = {}
|
|
|
|
for i = 1, #clients do
|
|
table.insert(_M.altTabTable, {
|
|
client = clients[i],
|
|
minimized = clients[i].minimized,
|
|
opacity = clients[i].opacity
|
|
})
|
|
end
|
|
end
|
|
|
|
-- If the length of list of clients is not equal to the length of altTabTable,
|
|
-- we need to repopulate the array and update the UI. This function does this
|
|
-- check.
|
|
function _M.clientsHaveChanged()
|
|
local clients = _M.getClients()
|
|
return _M.tableLength(clients) ~= _M.tableLength(_M.altTabTable)
|
|
end
|
|
|
|
function _M.createPreviewText(client)
|
|
if client.class then
|
|
return " " .. client.class
|
|
else
|
|
return " " .. client.name
|
|
end
|
|
end
|
|
|
|
-- Preview is created here.
|
|
function _M.clientOpacity()
|
|
if not _M.settings.client_opacity then return end
|
|
|
|
local opacity = _M.settings.client_opacity_value
|
|
if opacity > 1 then opacity = 1 end
|
|
for i,data in pairs(_M.altTabTable) do
|
|
data.client.opacity = opacity
|
|
end
|
|
|
|
if client.focus == _M.altTabTable[_M.altTabIndex].client then
|
|
-- Let's normalize the value up to 1.
|
|
local opacityFocusSelected = _M.settings.client_opacity_value_selected + _M.settings.client_opacity_value_in_focus
|
|
if opacityFocusSelected > 1 then opacityFocusSelected = 1 end
|
|
client.focus.opacity = opacityFocusSelected
|
|
else
|
|
-- Let's normalize the value up to 1.
|
|
local opacityFocus = _M.settings.client_opacity_value_in_focus
|
|
if opacityFocus > 1 then opacityFocus = 1 end
|
|
local opacitySelected = _M.settings.client_opacity_value_selected
|
|
if opacitySelected > 1 then opacitySelected = 1 end
|
|
|
|
client.focus.opacity = opacityFocus
|
|
_M.altTabTable[_M.altTabIndex].client.opacity = opacitySelected
|
|
end
|
|
end
|
|
|
|
-- This is called any _M.settings.preview_box_fps milliseconds. In case the list
|
|
-- of clients is changed, we need to redraw the whole preview box. Otherwise, a
|
|
-- simple widget::updated signal is enough
|
|
function _M.updatePreview()
|
|
if _M.clientsHaveChanged() then
|
|
_M.populateAltTabTable()
|
|
_M.preview()
|
|
end
|
|
|
|
for i = 1, #_M.preview_widgets do
|
|
_M.preview_widgets[i]:emit_signal("widget::updated")
|
|
end
|
|
end
|
|
|
|
function _M.cycle(dir)
|
|
-- Switch to next client
|
|
_M.altTabIndex = _M.altTabIndex + dir
|
|
if _M.altTabIndex > #_M.altTabTable then
|
|
_M.altTabIndex = 1 -- wrap around
|
|
elseif _M.altTabIndex < 1 then
|
|
_M.altTabIndex = #_M.altTabTable -- wrap around
|
|
end
|
|
|
|
_M.updatePreview()
|
|
|
|
_M.altTabTable[_M.altTabIndex].client.minimized = false
|
|
|
|
if not _M.settings.preview_box and not _M.settings.client_opacity then
|
|
client.focus = _M.altTabTable[_M.altTabIndex].client
|
|
end
|
|
|
|
if _M.settings.client_opacity and _M.preview_wbox.visible then
|
|
_M.clientOpacity()
|
|
end
|
|
|
|
if _M.settings.cycle_raise_client == true then
|
|
_M.altTabTable[_M.altTabIndex].client:raise()
|
|
end
|
|
end
|
|
|
|
function _M.preview()
|
|
if not _M.settings.preview_box then return end
|
|
|
|
-- Apply settings
|
|
_M.preview_wbox:set_bg(_M.settings.preview_box_bg)
|
|
_M.preview_wbox.border_color = _M.settings.preview_box_border
|
|
|
|
-- Make the wibox the right size, based on the number of clients
|
|
local n = math.max(7, #_M.altTabTable)
|
|
local W = screen[mouse.screen].geometry.width -- + 2 * _M.preview_wbox.border_width
|
|
local w = W / n -- widget width
|
|
local h = w * 0.75 -- widget height
|
|
local textboxHeight = w * 0.125
|
|
|
|
local x = screen[mouse.screen].geometry.x - _M.preview_wbox.border_width
|
|
local y = screen[mouse.screen].geometry.y + (screen[mouse.screen].geometry.height - h - textboxHeight) / 2
|
|
_M.preview_wbox:geometry({x = x, y = y, width = W, height = h + textboxHeight})
|
|
|
|
-- create a list that holds the clients to preview, from left to right
|
|
local leftRightTab = {}
|
|
local leftRightTabToAltTabIndex = {} -- save mapping from leftRightTab to altTabTable as well
|
|
local nLeft
|
|
local nRight
|
|
if #_M.altTabTable == 2 then
|
|
nLeft = 0
|
|
nRight = 2
|
|
else
|
|
nLeft = math.floor(#_M.altTabTable / 2)
|
|
nRight = math.ceil(#_M.altTabTable / 2)
|
|
end
|
|
|
|
for i = 1, nLeft do
|
|
table.insert(leftRightTab, _M.altTabTable[#_M.altTabTable - nLeft + i].client)
|
|
table.insert(leftRightTabToAltTabIndex, #_M.altTabTable - nLeft + i)
|
|
end
|
|
for i = 1, nRight do
|
|
table.insert(leftRightTab, _M.altTabTable[i].client)
|
|
table.insert(leftRightTabToAltTabIndex, i)
|
|
end
|
|
|
|
-- determine fontsize -> find maximum classname-length
|
|
local text, textWidth, textHeight, maxText
|
|
local maxTextWidth = 0
|
|
local maxTextHeight = 0
|
|
local bigFont = textboxHeight / 2
|
|
cr:set_font_size(fontSize)
|
|
for i = 1, #leftRightTab do
|
|
text = _M.createPreviewText(leftRightTab[i])
|
|
textWidth = cr:text_extents(text).width
|
|
textHeight = cr:text_extents(text).height
|
|
if textWidth > maxTextWidth or textHeight > maxTextHeight then
|
|
maxTextHeight = textHeight
|
|
maxTextWidth = textWidth
|
|
maxText = text
|
|
end
|
|
end
|
|
|
|
while true do
|
|
cr:set_font_size(bigFont)
|
|
textWidth = cr:text_extents(maxText).width
|
|
textHeight = cr:text_extents(maxText).height
|
|
|
|
if textWidth < w - textboxHeight and textHeight < textboxHeight then
|
|
break
|
|
end
|
|
|
|
bigFont = bigFont - 1
|
|
end
|
|
local smallFont = bigFont * _M.settings.preview_box_title_font_size_factor
|
|
|
|
_M.preview_widgets = {}
|
|
|
|
-- create all the widgets
|
|
for i = 1, #leftRightTab do
|
|
_M.preview_widgets[i] = wibox.widget.base.make_widget()
|
|
_M.preview_widgets[i].fit = function(preview_widget, width, height)
|
|
return w, h
|
|
end
|
|
local c = leftRightTab[i]
|
|
_M.preview_widgets[i].draw = function(preview_widget, preview_wbox, cr, width, height)
|
|
if width ~= 0 and height ~= 0 then
|
|
|
|
local a = 0.8
|
|
local overlay = 0.6
|
|
local fontSize = smallFont
|
|
if c == _M.altTabTable[_M.altTabIndex].client then
|
|
a = 0.9
|
|
overlay = 0
|
|
fontSize = bigFont
|
|
end
|
|
|
|
local sx, sy, tx, ty
|
|
|
|
-- Icons
|
|
local icon
|
|
if c.icon == nil then
|
|
icon = gears.surface(gears.surface.load(_M.noicon))
|
|
else
|
|
icon = gears.surface(c.icon)
|
|
end
|
|
|
|
local iconboxWidth = 0.9 * textboxHeight
|
|
local iconboxHeight = iconboxWidth
|
|
|
|
-- Titles
|
|
cr:select_font_face(unpack(_M.settings.preview_box_title_font))
|
|
cr:set_font_face(cr:get_font_face())
|
|
cr:set_font_size(fontSize)
|
|
|
|
text = _M.createPreviewText(c)
|
|
textWidth = cr:text_extents(text).width
|
|
textHeight = cr:text_extents(text).height
|
|
|
|
local titleboxWidth = textWidth + iconboxWidth
|
|
local titleboxHeight = textboxHeight
|
|
|
|
-- Draw icons
|
|
tx = (w - titleboxWidth) / 2
|
|
ty = h
|
|
sx = iconboxWidth / icon.width
|
|
sy = iconboxHeight / icon.height
|
|
|
|
cr:translate(tx, ty)
|
|
cr:scale(sx, sy)
|
|
cr:set_source_surface(icon, 0, 0)
|
|
cr:paint()
|
|
cr:scale(1/sx, 1/sy)
|
|
cr:translate(-tx, -ty)
|
|
|
|
-- Draw titles
|
|
tx = tx + iconboxWidth
|
|
ty = h + (textboxHeight + textHeight) / 2
|
|
|
|
cr:set_source_rgba(unpack(_M.settings.preview_box_title_color))
|
|
cr:move_to(tx, ty)
|
|
cr:show_text(text)
|
|
cr:stroke()
|
|
|
|
-- Draw previews
|
|
local cg = c:geometry()
|
|
if cg.width > cg.height then
|
|
sx = a * w / cg.width
|
|
sy = math.min(sx, a * h / cg.height)
|
|
else
|
|
sy = a * h / cg.height
|
|
sx = math.min(sy, a * h / cg.width)
|
|
end
|
|
|
|
tx = (w - sx * cg.width) / 2
|
|
ty = (h - sy * cg.height) / 2
|
|
|
|
local tmp = gears.surface(c.content)
|
|
cr:translate(tx, ty)
|
|
cr:scale(sx, sy)
|
|
cr:set_source_surface(tmp, 0, 0)
|
|
cr:paint()
|
|
tmp:finish()
|
|
|
|
-- Overlays
|
|
cr:scale(1/sx, 1/sy)
|
|
cr:translate(-tx, -ty)
|
|
cr:set_source_rgba(0,0,0,overlay)
|
|
cr:rectangle(tx, ty, sx * cg.width, sy * cg.height)
|
|
cr:fill()
|
|
end
|
|
end
|
|
|
|
-- Add mouse handler
|
|
_M.preview_widgets[i]:connect_signal("mouse::enter", function()
|
|
_M.cycle(leftRightTabToAltTabIndex[i] - _M.altTabIndex)
|
|
end)
|
|
end
|
|
|
|
-- Spacers left and right
|
|
local spacer = wibox.widget.base.make_widget()
|
|
spacer.fit = function(leftSpacer, width, height)
|
|
return (W - w * #_M.altTabTable) / 2, _M.preview_wbox.height
|
|
end
|
|
spacer.draw = function(preview_widget, preview_wbox, cr, width, height) end
|
|
|
|
--layout
|
|
preview_layout = wibox.layout.fixed.horizontal()
|
|
|
|
preview_layout:add(spacer)
|
|
for i = 1, #leftRightTab do
|
|
preview_layout:add(_M.preview_widgets[i])
|
|
end
|
|
preview_layout:add(spacer)
|
|
|
|
_M.preview_wbox:set_widget(preview_layout)
|
|
end
|
|
|
|
|
|
-- This starts the timer for updating and it shows the preview UI.
|
|
function _M.showPreview()
|
|
_M.preview_live_timer.timeout = 1 / _M.settings.preview_box_fps
|
|
_M.preview_live_timer:connect_signal("timeout", _M.updatePreview)
|
|
_M.preview_live_timer:start()
|
|
|
|
_M.preview()
|
|
_M.preview_wbox.visible = true
|
|
|
|
_M.clientOpacity()
|
|
end
|
|
|
|
function _M.switch(dir, mod_key1, release_key, mod_key2, key_switch)
|
|
_M.populateAltTabTable()
|
|
|
|
if #_M.altTabTable == 0 then
|
|
return
|
|
elseif #_M.altTabTable == 1 then
|
|
_M.altTabTable[1].client.minimized = false
|
|
_M.altTabTable[1].client:raise()
|
|
return
|
|
end
|
|
|
|
-- reset index
|
|
_M.altTabIndex = 1
|
|
|
|
-- preview delay timer
|
|
local previewDelay = _M.settings.preview_box_delay / 1000
|
|
_M.previewDelayTimer = timer({timeout = previewDelay})
|
|
_M.previewDelayTimer:connect_signal("timeout", function()
|
|
_M.previewDelayTimer:stop()
|
|
_M.showPreview()
|
|
end)
|
|
_M.previewDelayTimer:start()
|
|
|
|
-- Now that we have collected all windows, we should run a keygrabber
|
|
-- as long as the user is alt-tabbing:
|
|
keygrabber.run(
|
|
function (mod, key, event)
|
|
-- Stop alt-tabbing when the alt-key is released
|
|
if gears.table.hasitem(mod, mod_key1) then
|
|
if (key == release_key or key == "Escape") and event == "release" then
|
|
if _M.preview_wbox.visible == true then
|
|
_M.preview_wbox.visible = false
|
|
_M.preview_live_timer:stop()
|
|
else
|
|
_M.previewDelayTimer:stop()
|
|
end
|
|
|
|
if key == "Escape" then
|
|
for i = 1, #_M.altTabTable do
|
|
_M.altTabTable[i].client.opacity = _M.altTabTable[i].opacity
|
|
_M.altTabTable[i].client.minimized = _M.altTabTable[i].minimized
|
|
end
|
|
else
|
|
-- Raise clients in order to restore history
|
|
local c
|
|
for i = 1, _M.altTabIndex - 1 do
|
|
c = _M.altTabTable[_M.altTabIndex - i].client
|
|
if not _M.altTabTable[i].minimized then
|
|
c:raise()
|
|
client.focus = c
|
|
end
|
|
end
|
|
|
|
-- raise chosen client on top of all
|
|
c = _M.altTabTable[_M.altTabIndex].client
|
|
c:raise()
|
|
client.focus = c
|
|
|
|
-- restore minimized clients
|
|
for i = 1, #_M.altTabTable do
|
|
if i ~= _M.altTabIndex and _M.altTabTable[i].minimized then
|
|
_M.altTabTable[i].client.minimized = true
|
|
end
|
|
_M.altTabTable[i].client.opacity = _M.altTabTable[i].opacity
|
|
end
|
|
end
|
|
|
|
keygrabber.stop()
|
|
|
|
elseif key == key_switch and event == "press" then
|
|
if gears.table.hasitem(mod, mod_key2) then
|
|
-- Move to previous client on Shift-Tab
|
|
_M.cycle(-1)
|
|
else
|
|
-- Move to next client on each Tab-press
|
|
_M.cycle( 1)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
)
|
|
|
|
-- switch to next client
|
|
_M.cycle(dir)
|
|
|
|
end -- function altTab
|
|
|
|
return {switch = _M.switch, settings = _M.settings}
|