Reno is the second iteration of the AWMTK-powered AwesomeWM config.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

265 lines
10 KiB

-- This file is part of Reno desktop.
--
-- Reno desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
--
-- Reno desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License along with Reno desktop. If not, see <https://www.gnu.org/licenses/>.
-- Pulseaudio per-client volume setting
local awful = require("awful")
local gears = require("gears")
local wibox = require("wibox")
local awmtk2 = require("awmtk2")
local fastyaml = require("parsers").fast_split_yaml
local beautiful = require("beautiful")
local ask = require("asckey")
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
if _VERSION:match("5.1") then
result = (test_pactl == 0)
end
if not result then
return
end
local function get_icon(percent)
if percent >= 66 then
return beautiful["volume-high-symbolic"]
elseif percent >= 33 then
return beautiful["volume-medium-symbolic"]
elseif percent > 0 then
return beautiful["volume-low-symbolic"]
else
return beautiful["volume-muted-symbolic"]
end
end
return function(args)
local style = awmtk2.create_style("client_volume",
awmtk2.generic.oneline_widget, args.style)
local templates = awmtk2.create_template_lib("client_volume",awmtk2.templates,args.templates)
local t = awmtk2.build_templates(templates,style)
local widget = wibox.widget(t.container({
{
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)
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)
local slider_icon_container = wibox.widget(t.icon({
id = "client_volume_icon",
resize = true,
}))
local slider_icon = slider_icon_container:get_children_by_id("client_volume_icon")[1]
local slider_container = wibox.widget(t.slider({
minimum = 0,
maximum = 100,
id = "client_volume",
value = -1,
}))
local slider = slider_container:get_children_by_id("client_volume")[1]
local slider_touching = false
slider:connect_signal("widget::redraw_needed",function()
if slider_touching then
volume(sink_input_id,slider.value)
end
slider_icon.image = get_icon(slider.value)
end)
active_sliders[sink_input_id] = slider
slider:connect_signal("mouse::enter", function()
slider_touching = true
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
local function update_slider_list(c)
active_sliders = {}
client_volume_container:reset()
update_sliders: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
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(),
ask.k(":client.volume_up", function()
volume(client.focus,"+5")
end,{description = "increase client volume", group = "client"}),
ask.k(":client.volume_down", function()
volume(client.focus,"-5")
end,{description = "decrease client volume", group = "client"}),
ask.k(":client.volume_mute", function()
volume(client.focus,0)
end,{description = "mute client", group = "client"})
))
return widget
end