Fixed client volume widget; Added dynamic slider creation for media types
This commit is contained in:
parent
25c44f100a
commit
c7d942050c
|
@ -15,6 +15,53 @@ local beautiful = require("beautiful")
|
||||||
local ask = require("asckey")
|
local ask = require("asckey")
|
||||||
|
|
||||||
local test_pactl = os.execute("pactl --version")
|
local test_pactl = os.execute("pactl --version")
|
||||||
|
G_ClientSinksByPID = G_ClientSinksByPID or {}
|
||||||
|
G_ClientSinksByName = G_ClientSinksByName or {}
|
||||||
|
G_SinkVolumeLevels = G_SinkVolumeLevels or {}
|
||||||
|
G_SinkMediaTypes = G_SinkMediaTypes or {}
|
||||||
|
local update_client_volumes = function()
|
||||||
|
awful.spawn.easy_async("pactl -n \"awesome\" list sink-inputs",function(stdout)
|
||||||
|
local pactl_data = fastyaml(stdout)
|
||||||
|
local indexed_sinks = {}
|
||||||
|
local sinks_by_pid = {}
|
||||||
|
local sinks_by_name = {}
|
||||||
|
local sink_volume_levels = {}
|
||||||
|
local sink_media_types = {}
|
||||||
|
for _,v in pairs(pactl_data) do
|
||||||
|
local sink_id = tonumber(v:match("Sink Input #(%d+)"))
|
||||||
|
if sink_id then
|
||||||
|
if v:match("application.process.id = \"(%d+)\"") then
|
||||||
|
local pid = tonumber(v:match("application.process.id = \"(%d+)\""))
|
||||||
|
sinks_by_pid[pid] = sinks_by_pid[pid] or {}
|
||||||
|
table.insert(sinks_by_pid[pid], sink_id)
|
||||||
|
indexed_sinks[sink_id] = true
|
||||||
|
end
|
||||||
|
if v:match("application.name = \"([^\n]+)\"") then
|
||||||
|
local name = v:match("application.name = \"([^\n]+)\"")
|
||||||
|
sinks_by_name[name] = sinks_by_name[name] or {}
|
||||||
|
if not indexed_sinks[sink_id] then
|
||||||
|
indexed_sinks[sink_id] = true
|
||||||
|
table.insert(sinks_by_name[name], sink_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if indexed_sinks[sink_id] then
|
||||||
|
sink_volume_levels[sink_id] = tonumber(v:match("Volume: .-(%d+)%%"))
|
||||||
|
sink_media_types[sink_id] = v:match("media.name = \"([^\"]+)\"") or
|
||||||
|
v:match("media.class = \"([^\"]+)\"")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
G_ClientSinksByName = sinks_by_name
|
||||||
|
G_ClientSinksByPID = sinks_by_pid
|
||||||
|
G_SinkVolumeLevels = sink_volume_levels
|
||||||
|
G_SinkMediaTypes = sink_media_types
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
G_ClientSinksUpdateTimer = G_ClientSinksUpdateTimer or gears.timer({
|
||||||
|
timeout = 0.5,
|
||||||
|
autostart = true,
|
||||||
|
callback = update_client_volumes
|
||||||
|
})
|
||||||
local result = test_pactl
|
local result = test_pactl
|
||||||
if _VERSION:match("5.1") then
|
if _VERSION:match("5.1") then
|
||||||
result = (test_pactl == 0)
|
result = (test_pactl == 0)
|
||||||
|
@ -38,117 +85,182 @@ end
|
||||||
|
|
||||||
return function(args)
|
return function(args)
|
||||||
local style = awmtk2.create_style("client_volume",
|
local style = awmtk2.create_style("client_volume",
|
||||||
awmtk2.generic.oneline_widget, args.style,args.vertical)
|
awmtk2.generic.oneline_widget, args.style)
|
||||||
local templates = awmtk2.create_template_lib("client_volume",awmtk2.templates,args.templates)
|
local templates = awmtk2.create_template_lib("client_volume",awmtk2.templates,args.templates)
|
||||||
local t = awmtk2.build_templates(templates,style,args.vertical)
|
local t = awmtk2.build_templates(templates,style)
|
||||||
local widget = wibox.widget(t.container({
|
local widget = wibox.widget(t.container({
|
||||||
t.icon({
|
{
|
||||||
|
t.icon({
|
||||||
|
image = get_icon(0);
|
||||||
|
resize = true
|
||||||
|
}),
|
||||||
|
t.textbox({
|
||||||
|
markup = "No sound/Not available"
|
||||||
|
}),
|
||||||
|
visible = true,
|
||||||
|
id = "error",
|
||||||
|
spacing = style.base.spacing,
|
||||||
|
layout = wibox.layout.fixed.horizontal
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id = "client_volume_container",
|
||||||
|
spacing = style.base.spacing,
|
||||||
|
layout = wibox.layout.fixed.vertical
|
||||||
|
},
|
||||||
|
spacing = style.base.spacing,
|
||||||
|
layout = wibox.layout.fixed.vertical
|
||||||
|
}))
|
||||||
|
local client_volume_container = widget:get_children_by_id("client_volume_container")[1]
|
||||||
|
local errorbox = widget:get_children_by_id("error")[1]
|
||||||
|
local id_by_slider_container = {}
|
||||||
|
local active_sliders = {}
|
||||||
|
-- Asynchronous promise for a "create_slider" function
|
||||||
|
local create_slider = function(sink_input_id) end
|
||||||
|
local remove_slider = function(sink_input_id)
|
||||||
|
require('naughty').notify({title = "remove_slider called", text=tostring(sink_input_id)})
|
||||||
|
local index_to_remove = nil
|
||||||
|
for k,v in pairs(client_volume_container.children) do
|
||||||
|
if id_by_slider_container[v] == sink_input_id then
|
||||||
|
index_to_remove = k
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if index_to_remove then
|
||||||
|
active_sliders[sink_input_id] = nil
|
||||||
|
client_volume_container:remove(index_to_remove)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- Callback to update all slider values
|
||||||
|
local function update_active_sliders()
|
||||||
|
local checked_sliders = {}
|
||||||
|
if client.focus and client.focus.name then
|
||||||
|
for _,v in pairs(G_ClientSinksByName[client.focus.name] or {}) do
|
||||||
|
checked_sliders[v] = true
|
||||||
|
if not active_sliders[v] then
|
||||||
|
create_slider(v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if client.focus and client.focus.pid then
|
||||||
|
for _,v in pairs(G_ClientSinksByPID[client.focus.pid] or {}) do
|
||||||
|
checked_sliders[v] = true
|
||||||
|
if not active_sliders[v] then
|
||||||
|
create_slider(v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for k,_ in pairs(active_sliders) do
|
||||||
|
if not checked_sliders[k] then
|
||||||
|
remove_slider(k)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for sink_input_id,slider in pairs(active_sliders) do
|
||||||
|
slider.value = G_SinkVolumeLevels[sink_input_id] or -1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- Update sliders every 0.5 seconds
|
||||||
|
local update_sliders = gears.timer({
|
||||||
|
timeout = 0.5,
|
||||||
|
autostart = true,
|
||||||
|
callback = update_active_sliders
|
||||||
|
})
|
||||||
|
-- Function to set client volume
|
||||||
|
local function volume(filter,value)
|
||||||
|
update_sliders:again()
|
||||||
|
if type(filter) == "number" then
|
||||||
|
awful.spawn("pactl set-sink-input-volume "..tostring(filter).." "..tostring(value).."%")
|
||||||
|
elseif filter then
|
||||||
|
if filter.name then
|
||||||
|
for _,v in pairs(G_ClientSinksByName[filter.name] or {}) do
|
||||||
|
awful.spawn("pactl set-sink-input-volume "..tostring(v).." "..tostring(value).."%")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if filter.pid then
|
||||||
|
for _,v in pairs(G_ClientSinksByPID[filter.pid] or {}) do
|
||||||
|
awful.spawn("pactl set-sink-input-volume "..tostring(v).." "..tostring(value).."%")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
create_slider = function(sink_input_id)
|
||||||
|
require('naughty').notify({title = "create_slider called", text=tostring(sink_input_id)})
|
||||||
|
local slider_icon_container = wibox.widget(t.icon({
|
||||||
id = "client_volume_icon",
|
id = "client_volume_icon",
|
||||||
resize = true,
|
resize = true,
|
||||||
}),
|
}))
|
||||||
(args.vertical and {
|
local slider_icon = slider_icon_container:get_children_by_id("client_volume_icon")[1]
|
||||||
t.textbox({
|
local slider_container = wibox.widget(t.slider({
|
||||||
id = "error"
|
|
||||||
}),
|
|
||||||
widget = wibox.container.rotate,
|
|
||||||
direction = "east"
|
|
||||||
}) or t.textbox({
|
|
||||||
id = "error"
|
|
||||||
}),
|
|
||||||
t.slider({
|
|
||||||
minimum = 0,
|
minimum = 0,
|
||||||
maximum = 100,
|
maximum = 100,
|
||||||
id = "client_volume",
|
id = "client_volume",
|
||||||
value = -1
|
value = -1,
|
||||||
}),
|
}))
|
||||||
layout = (args.vertical and wibox.layout.fixed.vertical) or
|
local slider = slider_container:get_children_by_id("client_volume")[1]
|
||||||
wibox.layout.fixed.horizontal
|
local slider_touching = false
|
||||||
}))
|
slider:connect_signal("widget::redraw_needed",function()
|
||||||
local errorbox = widget:get_children_by_id("error")[1]
|
if slider_touching then
|
||||||
local icon = widget:get_children_by_id("client_volume_icon")[1]
|
volume(sink_input_id,slider.value)
|
||||||
local slider = widget:get_children_by_id("client_volume")[1]
|
|
||||||
-- Local tracking value to prevent zero volume on start
|
|
||||||
local touched = false
|
|
||||||
-- Attach to focus change
|
|
||||||
client.connect_signal("update_volume",function(c)
|
|
||||||
awful.spawn.easy_async("pactl list sink-inputs",function(stdout)
|
|
||||||
local pactl_data = fastyaml(stdout)
|
|
||||||
local cl
|
|
||||||
for _,v in pairs(pactl_data) do
|
|
||||||
if not c then return end
|
|
||||||
if v:match("application.process.id = \""..tostring(c.pid).."\"") then
|
|
||||||
cl = v
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
if not cl then
|
slider_icon.image = get_icon(slider.value)
|
||||||
slider.visible = false
|
|
||||||
errorbox.visible = true
|
|
||||||
errorbox:set_markup("No sound/Not available")
|
|
||||||
icon:set_image(beautiful["volume-muted-symbolic"])
|
|
||||||
return
|
|
||||||
end
|
|
||||||
local volume = tonumber(cl:match("Volume:[^\n]-(%d*)%%"))
|
|
||||||
slider.visible = true
|
|
||||||
errorbox.visible = false
|
|
||||||
icon:set_image(get_icon(volume))
|
|
||||||
slider.value = volume
|
|
||||||
touched = true
|
|
||||||
end)
|
end)
|
||||||
end)
|
active_sliders[sink_input_id] = slider
|
||||||
client.connect_signal("focus",function(c)
|
slider:connect_signal("mouse::enter", function()
|
||||||
touched = false
|
slider_touching = true
|
||||||
c:emit_signal("update_volume")
|
|
||||||
end)
|
|
||||||
local update_timer = gears.timer({
|
|
||||||
timeout = 0.5,
|
|
||||||
autostart = true,
|
|
||||||
callback = function()
|
|
||||||
if client.focus then
|
|
||||||
client.focus:emit_signal("update_volume")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
})
|
|
||||||
-- Async lock to prevent callback interference
|
|
||||||
local volume_lock = false
|
|
||||||
-- Function to set client volume
|
|
||||||
local function volume(value)
|
|
||||||
if volume_lock then return end
|
|
||||||
volume_lock = true
|
|
||||||
awful.spawn.easy_async("pactl list sink-inputs",function(stdout)
|
|
||||||
local pactl_data = fastyaml(stdout)
|
|
||||||
if not (client.focus and client.focus.pid) then
|
|
||||||
volume_lock = false
|
|
||||||
return
|
|
||||||
end
|
|
||||||
for _,v in pairs(pactl_data) do
|
|
||||||
if v:match("application.process.id = \""..tostring(client.focus.pid).."\"") then
|
|
||||||
local sink_id = v:match("^%s*Sink Input #(%d+)")
|
|
||||||
if sink_id then
|
|
||||||
print(sink_id, value)
|
|
||||||
awful.spawn("pactl set-sink-input-volume "..tostring(sink_id).." "..tostring(value).."%")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
volume_lock = false
|
|
||||||
end)
|
end)
|
||||||
|
slider:connect_signal("mouse::leave", function()
|
||||||
|
slider_touching = false
|
||||||
|
end)
|
||||||
|
local new_widget = wibox.widget({
|
||||||
|
t.textbox({
|
||||||
|
markup = G_SinkMediaTypes[sink_input_id],
|
||||||
|
ellipsize = "end",
|
||||||
|
forced_width = style.slider.width,
|
||||||
|
forced_height = style.slider.height*(2/3)
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
slider_icon_container,
|
||||||
|
slider_container,
|
||||||
|
layout = wibox.layout.fixed.horizontal
|
||||||
|
},
|
||||||
|
spacing = style.base.spacing,
|
||||||
|
layout = wibox.layout.fixed.vertical,
|
||||||
|
id = tostring(sink_input_id)
|
||||||
|
})
|
||||||
|
client_volume_container:add(new_widget)
|
||||||
|
id_by_slider_container[new_widget] = sink_input_id
|
||||||
end
|
end
|
||||||
-- Attach change to slider
|
local function update_slider_list(c)
|
||||||
slider:connect_signal("widget::redraw_needed",function()
|
active_sliders = {}
|
||||||
if touched then
|
client_volume_container:reset()
|
||||||
volume(slider.value)
|
update_sliders:again()
|
||||||
update_timer:again()
|
local count = false
|
||||||
|
if c.name then
|
||||||
|
for _,v in pairs(G_ClientSinksByName[c.name] or {}) do
|
||||||
|
create_slider(v)
|
||||||
|
count = true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end)
|
if c.pid then
|
||||||
|
for _,v in pairs(G_ClientSinksByPID[c.pid] or {}) do
|
||||||
|
create_slider(v)
|
||||||
|
count = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
update_active_sliders()
|
||||||
|
errorbox.visible = not count
|
||||||
|
end
|
||||||
|
-- Attach to focus change
|
||||||
|
client.connect_signal("focus",update_slider_list)
|
||||||
|
-- Update
|
||||||
root.keys(gears.table.join(
|
root.keys(gears.table.join(
|
||||||
root.keys(),
|
root.keys(),
|
||||||
ask.k(":client.volume_up", function()
|
ask.k(":client.volume_up", function()
|
||||||
volume("+5")
|
volume(client.focus,"+5")
|
||||||
end,{description = "increase client volume", group = "client"}),
|
end,{description = "increase client volume", group = "client"}),
|
||||||
ask.k(":client.volume_down", function()
|
ask.k(":client.volume_down", function()
|
||||||
volume("-5")
|
volume(client.focus,"-5")
|
||||||
end,{description = "decrease client volume", group = "client"}),
|
end,{description = "decrease client volume", group = "client"}),
|
||||||
ask.k(":client.volume_mute", function()
|
ask.k(":client.volume_mute", function()
|
||||||
volume(0)
|
volume(client.focus,0)
|
||||||
end,{description = "mute client", group = "client"})
|
end,{description = "mute client", group = "client"})
|
||||||
))
|
))
|
||||||
return widget
|
return widget
|
||||||
|
|
Loading…
Reference in New Issue