bling/module/tabbed.lua

275 lines
8.8 KiB
Lua
Raw Normal View History

2020-11-07 18:16:16 +01:00
--[[
This module currently works by adding a new property to each client that is tabbed.
That new property is called bling_tabbed.
So each client in a tabbed state has the property "bling_tabbed" which is a table.
Each client that is not tabbed doesn't have that property.
In the function themselves, the same object is refered to as "tabobj" which is why
you will often see something like: "local tabobj = some_client.bling_tabbed" at the beginning
of a function.
--]]
local awful = require("awful")
2020-11-07 18:16:16 +01:00
local wibox = require("wibox")
local gears = require("gears")
local beautiful = require("beautiful")
2021-01-23 23:04:22 +01:00
local helpers = require(tostring(...):match(".*bling") .. ".helpers")
2020-11-07 18:16:16 +01:00
2020-11-22 10:18:39 +01:00
local bar_style = beautiful.tabbar_style or "default"
2021-08-27 20:01:22 +02:00
local bar = require(
tostring(...):match(".*bling") .. ".widget.tabbar." .. bar_style
)
2020-11-07 18:16:16 +01:00
tabbed = {}
-- helper function to connect to the (un)focus signals
local function update_tabbar_from(c)
if not c or not c.bling_tabbed then
return
end
tabbed.update_tabbar(c.bling_tabbed)
end
2021-08-27 20:01:22 +02:00
-- used to change focused tab relative to the currently focused one
2020-11-07 18:16:16 +01:00
tabbed.iter = function(idx)
2021-08-27 20:01:22 +02:00
if not idx then
idx = 1
end
if not client.focus or not client.focus.bling_tabbed then
return
end
2020-11-07 18:16:16 +01:00
local tabobj = client.focus.bling_tabbed
local new_idx = (tabobj.focused_idx + idx) % #tabobj.clients
2021-08-27 20:01:22 +02:00
if new_idx == 0 then
new_idx = #tabobj.clients
end
2020-11-07 18:16:16 +01:00
tabbed.switch_to(tabobj, new_idx)
end
2020-11-07 18:16:16 +01:00
2020-11-09 15:59:36 +01:00
-- removes a given client from its tab object
tabbed.remove = function(c)
2021-08-27 20:01:22 +02:00
if not c or not c.bling_tabbed then
return
end
2020-11-09 15:59:36 +01:00
local tabobj = c.bling_tabbed
table.remove(tabobj.clients, tabobj.focused_idx)
if not beautiful.tabbar_disable then
awful.titlebar.hide(c, bar.position)
end
2020-11-09 15:59:36 +01:00
c.bling_tabbed = nil
c:disconnect_signal("focus", update_tabbar_from)
c:disconnect_signal("unfocus", update_tabbar_from)
awesome.emit_signal("bling::tabbed::client_removed", tabobj, c)
2020-11-07 18:16:16 +01:00
tabbed.switch_to(tabobj, 1)
2020-11-09 15:59:36 +01:00
end
-- removes the currently focused client from the tab object
tabbed.pop = function()
2021-08-27 20:01:22 +02:00
if not client.focus or not client.focus.bling_tabbed then
return
end
2020-11-09 15:59:36 +01:00
tabbed.remove(client.focus)
end
2020-11-07 18:16:16 +01:00
2020-11-09 15:59:36 +01:00
-- adds a client to a given tabobj
tabbed.add = function(c, tabobj)
if c.bling_tabbed then
tabbed.remove(c)
end
c:connect_signal("focus", update_tabbar_from)
c:connect_signal("unfocus", update_tabbar_from)
2021-03-15 13:50:25 +01:00
helpers.client.sync(c, tabobj.clients[tabobj.focused_idx])
tabobj.clients[#tabobj.clients + 1] = c
2020-11-09 15:59:36 +01:00
tabobj.focused_idx = #tabobj.clients
-- calls update even though switch_to calls update again
2021-08-27 20:01:22 +02:00
-- but the new client needs to have the tabobj property
2020-11-09 15:59:36 +01:00
-- before a clean switch can happen
tabbed.update(tabobj)
awesome.emit_signal("bling::tabbed::client_added", tabobj, c)
2020-11-09 15:59:36 +01:00
tabbed.switch_to(tabobj, #tabobj.clients)
end
-- use xwininfo to select one client and make it tab in the currently focused tab
2020-11-09 15:59:36 +01:00
tabbed.pick = function()
2021-08-27 20:01:22 +02:00
if not client.focus then
return
end
-- this function uses xwininfo to grab a client window id which is then
-- compared to all other clients window ids
2021-08-27 20:01:22 +02:00
local xwininfo_cmd =
[[ xwininfo | grep 'xwininfo: Window id:' | cut -d " " -f 4 ]]
awful.spawn.easy_async_with_shell(xwininfo_cmd, function(output)
for _, c in ipairs(client.get()) do
if tonumber(c.window) == tonumber(output) then
if not client.focus.bling_tabbed and not c.bling_tabbed then
tabbed.init(client.focus)
tabbed.add(c, client.focus.bling_tabbed)
end
if not client.focus.bling_tabbed and c.bling_tabbed then
tabbed.add(client.focus, c.bling_tabbed)
end
if client.focus.bling_tabbed and not c.bling_tabbed then
tabbed.add(c, client.focus.bling_tabbed)
end
-- TODO: Should also merge tabs when focus and picked
-- both are tab groups
end
end
end)
2020-11-07 18:16:16 +01:00
end
-- select a client by direction and make it tab in the currently focused tab
2021-08-27 20:01:22 +02:00
tabbed.pick_by_direction = function(direction)
local sel = client.focus
2021-08-27 20:01:22 +02:00
if not sel then
return
end
if not sel.bling_tabbed then
tabbed.init(sel)
end
local c = helpers.client.get_by_direction(direction)
2021-08-27 20:01:22 +02:00
if not c then
return
end
tabbed.add(c, sel.bling_tabbed)
end
2021-08-27 20:01:22 +02:00
-- use dmenu to select a client and make it tab in the currently focused tab
2020-12-20 20:38:13 +01:00
tabbed.pick_with_dmenu = function(dmenu_command)
2021-08-27 20:01:22 +02:00
if not client.focus then
return
end
2020-12-20 20:38:13 +01:00
2021-08-27 20:01:22 +02:00
if not dmenu_command then
dmenu_command = "rofi -dmenu -i"
end
2020-12-20 20:38:13 +01:00
-- get all clients from the current tag
-- ignores the case where multiple tags are selected
local t = awful.screen.focused().selected_tag
local list_clients = {}
local list_clients_string = ""
for idx, c in ipairs(t:clients()) do
if c.window ~= client.focus.window then
2020-12-20 20:38:13 +01:00
list_clients[#list_clients + 1] = c
if #list_clients ~= 1 then
list_clients_string = list_clients_string .. "\\n"
end
2021-08-27 20:01:22 +02:00
list_clients_string = list_clients_string
.. tostring(c.window)
.. " "
.. c.name
2020-12-20 20:38:13 +01:00
end
end
2021-08-27 20:01:22 +02:00
if #list_clients == 0 then
return
end
2020-12-20 20:38:13 +01:00
-- calls the actual dmenu
2021-08-27 20:01:22 +02:00
local xprop_cmd = [[ echo -e "]]
.. list_clients_string
.. [[" | ]]
.. dmenu_command
.. [[ | awk '{ print $1 }' ]]
2020-12-20 20:38:13 +01:00
awful.spawn.easy_async_with_shell(xprop_cmd, function(output)
for _, c in ipairs(list_clients) do
if tonumber(c.window) == tonumber(output) then
2021-08-27 20:01:22 +02:00
if not client.focus.bling_tabbed then
tabbed.init(client.focus)
end
local tabobj = client.focus.bling_tabbed
2020-12-20 20:38:13 +01:00
tabbed.add(c, tabobj)
end
end
end)
end
2020-11-09 15:59:36 +01:00
-- update everything about one tab object
tabbed.update = function(tabobj)
2020-11-07 18:16:16 +01:00
local currently_focused_c = tabobj.clients[tabobj.focused_idx]
2020-11-09 15:59:36 +01:00
-- update tabobj of each client and other things
for idx, c in ipairs(tabobj.clients) do
if c.valid then
2020-11-09 15:59:36 +01:00
c.bling_tabbed = tabobj
2021-03-15 13:50:25 +01:00
helpers.client.sync(c, currently_focused_c)
2020-11-09 15:59:36 +01:00
-- the following handles killing a client while the client is tabbed
2021-08-27 20:01:22 +02:00
c:connect_signal("unmanage", function(c)
tabbed.remove(c)
end)
2020-11-09 15:59:36 +01:00
end
end
-- Maybe remove if I'm the only one using it?
awesome.emit_signal("bling::tabbed::update", tabobj)
2021-08-27 20:01:22 +02:00
if not beautiful.tabbar_disable then
tabbed.update_tabbar(tabobj)
end
2020-11-07 18:16:16 +01:00
end
-- change focused tab by absolute index
2020-11-07 18:16:16 +01:00
tabbed.switch_to = function(tabobj, new_idx)
local old_focused_c = tabobj.clients[tabobj.focused_idx]
tabobj.focused_idx = new_idx
for idx, c in ipairs(tabobj.clients) do
if idx ~= new_idx then
2021-01-23 23:04:22 +01:00
helpers.client.turn_off(c)
else
2021-01-23 23:04:22 +01:00
helpers.client.turn_on(c)
2020-11-07 18:16:16 +01:00
c:raise()
if old_focused_c and old_focused_c.valid then
2020-11-09 15:59:36 +01:00
c:swap(old_focused_c)
end
2021-03-15 13:50:25 +01:00
helpers.client.sync(c, old_focused_c)
2020-11-09 15:59:36 +01:00
end
end
awesome.emit_signal("bling::tabbed::changed_focus", tabobj)
2020-11-07 18:16:16 +01:00
tabbed.update(tabobj)
end
tabbed.update_tabbar = function(tabobj)
2020-11-09 15:59:36 +01:00
local flexlist = bar.layout()
local tabobj_focused_client = tabobj.clients[tabobj.focused_idx]
local tabobj_is_focused = (client.focus == tabobj_focused_client)
2020-11-07 18:16:16 +01:00
-- itearte over all tabbed clients to create the widget tabbed list
for idx, c in ipairs(tabobj.clients) do
2021-08-27 20:01:22 +02:00
local buttons = gears.table.join(awful.button({}, 1, function()
tabbed.switch_to(tabobj, idx)
end))
local wid_temp = bar.create(c, (idx == tabobj.focused_idx), buttons,
not tabobj_is_focused)
2020-11-07 18:16:16 +01:00
flexlist:add(wid_temp)
end
2020-11-07 18:16:16 +01:00
-- add tabbar to each tabbed client (clients will be hided anyway)
for _, c in ipairs(tabobj.clients) do
2020-11-07 18:16:16 +01:00
local titlebar = awful.titlebar(c, {
bg = bar.bg_normal,
2020-11-15 10:05:56 +01:00
size = bar.size,
2021-08-27 20:01:22 +02:00
position = bar.position,
2020-11-07 18:16:16 +01:00
})
2021-08-27 20:01:22 +02:00
titlebar:setup({ layout = wibox.layout.flex.horizontal, flexlist })
end
end
2020-11-07 18:16:16 +01:00
tabbed.init = function(c)
2020-11-09 15:59:36 +01:00
local tabobj = {}
2021-08-27 20:01:22 +02:00
tabobj.clients = { c }
c:connect_signal("focus", update_tabbar_from)
c:connect_signal("unfocus", update_tabbar_from)
2020-11-09 15:59:36 +01:00
tabobj.focused_idx = 1
tabbed.update(tabobj)
2020-11-07 18:16:16 +01:00
end
if beautiful.tabbed_spawn_in_tab then
2020-11-15 10:05:56 +01:00
client.connect_signal("manage", function(c)
local s = awful.screen.focused()
local previous_client = awful.client.focus.history.get(s, 1)
if previous_client and previous_client.bling_tabbed then
2020-11-15 10:05:56 +01:00
tabbed.add(c, previous_client.bling_tabbed)
end
end)
end
2020-11-07 18:16:16 +01:00
return tabbed